Template Changes in each Release Reference

This document contains reference material for the ActionKit Original Templateset, specifically the changes made to templates for each release. Included here are the diffs of the changes made.

If your templateset is connected to GitHub, make sure you are applying these changes manually -- otherwise you will not have the benefits of these new features and bugfixes. Consider forking the published version of Original. Integrating new features and fixes into your customized fork can be as easy as a pull request and a few clicks.

2.5.15 (2023)

2.5.10 (2022 Oct 24)

Prevent double-submit of donation forms

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index 5445c1b462..b390edfc6f 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -1300,12 +1300,17 @@
         /* clear paypal=1 hidden field, only needed if someone hit back */
         $('#ak-pay-by-paypal').val(0);
 
+        if ($('button.ak-submit-button').hasClass('is-submitted')) {
+            return false
+        }
+
         if ( $('form.ak-donate-three-step').length > 0 ) {
             validate_step("3");
 
             if (step_has_errors) {
                 return false;
             } else {
+                $('button.ak-submit-button').addClass('is-submitted');
                 return true;
             }
         }
@@ -1318,12 +1323,19 @@
         results = [step_1_validation(),
                    step_2_validation(),
                    step_3_validation()];
-        return !(results[0] || results[1] || results[2]);
+        var ok = !(results[0] || results[1] || results[2]);
+        if (ok) {
+            $('button.ak-submit-button').addClass('is-submitted');
+        }
+        return ok
     }
 
     $( function () {
         $('#ak-paypal-button').on('click', submit_paypal);
     	$('.ak-submit-button').on('click', submit_cc);
+        $(window).on('pagehide', function() {
+            $('button.ak-submit-button').removeClass('is-submitted');
+        });
 
         {% if page.paypal_user_requirements == "none" %}
         $('#ak-paypal-button').on('mousedown', function () {

2.4.29 (2022 Jan 11)

3-step donation flow after a successful donation

qdiff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index bd19de9c63..01c3f25c45 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -843,6 +843,19 @@
     function three_step_advance() {
         // trigger validation on the step we're leaving
         var current_step = $('input[name="ak-donate-step"]:checked').val();
+	// hitting "back" after donating confuses our step navigation.
+	// User gets dropped on first step, then browser autocomplete sets our step tracker field without us knowing.
+	// So, check what step the UI's on, and if step tracker and UI disagree give precedence to the UI.
+	var currentStepUI = $('div.ak-donate-area-step-1,div.ak-donate-area-step-2,div.ak-donate-area-step-3').filter(':visible');
+	if(currentStepUI.length){
+	    var uiStepNum = currentStepUI.attr('class')
+		.match(/ak-donate-area-step-[0-9]/g)[0]
+		.slice(-1);
+	    if(uiStepNum != current_step){
+		$('input[name=ak-donate-step][value=' + uiStepNum + ']').prop('checked', true);
+		current_step = uiStepNum;
+	    }
+	}
         validate_step(current_step);
 
         if (!step_has_errors) {

Progress (goal) thermometer now supports original currencies, not just converted to US dollars

diff --git a/db_templates/Original/progress_meter.html b/db_templates/Original/progress_meter.html
index 7be86a15be..09e5c55324 100644
--- a/db_templates/Original/progress_meter.html
+++ b/db_templates/Original/progress_meter.html
@@ -6,7 +6,7 @@
     <script type="text/ak-template" for="progress">
         [% with (progress) { %]
             [% if ( goal && total ) { %]
-                [% progress.current = goal_type == 'dollars' ? total.dollars : total.actions; %]
+                [% progress.current = goal_type == 'dollars' ? total.dollars : goal_type == 'money' ? total.money : total.actions; %]
                 [% progress.percent = parseInt(progress.current/goal*100); %]
                 <div class="ak-progress-holder">
                     <div class="ak-progress-meter-border">
@@ -18,9 +18,14 @@
                     <div class="ak-progress-goals">
                         [% if (goal_type == "dollars") { %]
                             <span class="ak-progress-actions">
-                                {{ page.currency_sym|default:"$" }}[%= add_commas(total.dollars) %] raised so far 
+                                $[%= add_commas(total.dollars) %] raised so far
                             </span> <br> 
-                            of our goal of {{ page.currency_sym|default:"$" }}[%= add_commas(goal)  %]!
+                            of our goal of $[%= add_commas(goal)  %]!
+                        [% } else if (goal_type == "money") { %]
+                             <span class="ak-progress-actions">
+                                 {{ page.currency|iso_currency_symbol }}[%= add_commas(total.money) %] raised so far
+                            </span> <br>
+                            of our goal of {{ page.currency|iso_currency_symbol }}[%= add_commas(goal)  %]!
                         [% } else { %]  
                             <span class="ak-progress-actions">
                                 [%= add_commas(total.actions) %] [%= total.actions != 1 ? 'actions' : 'action' %] taken so far 

2.4.27

No changes.

2.4.26 (2021 Nov 2)

No changes.

2.4.25 (2021 Sept 10)

PayFlow Pro donations via VeryGoodSecurity

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index 4c0f07facc..97182372f2 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -1512,15 +1512,8 @@ $(function() {
   <script type="text/javascript" src="https://js.verygoodvault.com/vgs-collect/2.8/vgs-collect.js"></script>
   <script src="/resources/ak_vgs.js"></script>
   <script>
-     $(function(){
-         var fastActionOptions = {
-             spinnerReplaces: '.ak-field-box', // element temporarily replaced by spinner
-             ccSavePlacement: { before: '.ak-submit-button' },
-             badgePlacement: { before: '.ak-field-box' }, // or after, container
-             // requireComplianceFields: 1, // require employer and occupation
-             vgs: {
-                 form: actionkit.form,
+    var vgs = {
         token: '{{ pp.client_token }}',
         environment: '{{ pp.vgs_environment }}',
         submitOnEmpty: function() { return $('#ak-pay-by-paypal').val() == 1; },
         rawFieldSelectors: ["#ak-card_num", "#ak-card_code", "#ak-exp_date-hosted"],
@@ -1528,7 +1521,18 @@ $(function() {
                    "color": "black", "successColor": "green", "errorColor": "#d00", "fontSize": "15.5px"},
                  {"selector": "#ak-card_code-hosted", "name": "card_code", "type": "card-security-code", "placeholder": "",
                   "validations": ["required", "validCardSecurityCode"], "color": "black",
-                           "successColor": "green", "fontSize": "15.5px", "errorColor": "#d00"}]}};
+                  "successColor": "green", "fontSize": "15.5px", "errorColor": "#d00"}]};
+
+    {% if pp.supports_oneclick %}
+       $(function(){
+         vgs.form = actionkit.form;
+         var fastActionOptions = {
+             spinnerReplaces: '.ak-field-box', // element temporarily replaced by spinner
+             ccSavePlacement: { before: '.ak-submit-button' },
+             badgePlacement: { before: '.ak-field-box' }, // or after, container
+             // requireComplianceFields: 1, // require employer and occupation
+             vgs: vgs
+         };
          var faForm = new actionkit.forms.fastaction.form(actionkit, fastActionOptions);
          faForm.init()
            .then(faForm.processOneclick)
@@ -1539,6 +1543,12 @@ $(function() {
                }
            })
        });
+    {% else %}
+      $(function(){
+         vgs.form = actionkit.form;
+         actionkit.donations.vgs.initClient(vgs);
+      });
+    {% endif %}
   </script>
 {% endif %}
 {% endwith %}

2.4.24 (2021 Aug 26)

FastAction Oneclick Donations

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index f7393bbf76..4c0f07facc 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -1,5 +1,5 @@
 {% extends "./wrapper.html" %}
-{% block body_extra_classes %}{% if page.payment_processor_information.is_quickdonate %}actionkit-donate-qd {% endif %}{{ block.super }}{% endblock %}
+{% block body_extra_classes %}{% if page.payment_processor_information.is_oneclick %}actionkit-donate-fa {% endif %}{{ block.super }}{% endblock %}
 {% block content %}
 <form id="act" name="act" class="{{ templateset.custom_fields.donation_steps }}" method="POST" action="/act/" accept-charset="utf-8">
     <input type="hidden" name="page" value="{{ page.name }}">
@@ -538,7 +538,7 @@
 
                 </div><!--ak-styled-fields-->
 
-                <button class="ak-donate-three-step-visible" id="ak-continue-button">Continue</button>
+                <button class="ak-donate-three-step-visible" id="ak-continue-button" type="button">Continue</button>
 
                 {% if page.payment_processor_information.init_submit_disabled %}
 
@@ -567,7 +567,7 @@
                         <button type="submit" id="ak-paypal-button" class="ak-inline ak-paypal-button"><img src="/media/modern/paypal-logo-1.svg"></button>
                     </div>
                 {% endif %}
-
+                {% fastaction %}
             </div>
 
             </div>
@@ -974,7 +974,11 @@
                     step_has_errors = true;
                 }
             }
-
+	} else if (actionkit.donations && actionkit.donations.vgs){
+	    actionkit.donations.vgs.validateHostedForm().map(function(error){
+		actionkit.donations.vgs.handleError(error);
+		step_has_errors = true;	
+	    });
         } else {
             if (!do_validate_credit_card()) {
                 return step_has_errors;
@@ -1107,7 +1111,7 @@
             } else {
                 // otherwise show all steps
                 $('.ak-donate-area-step-1, .ak-donate-area-step-2, .ak-donate-area-step-3').show();
-                $('#ak-continue-button').hide();
+                $('#ak-continue-button, .ak-donate-three-step-visible').hide();
             }
         }
         three_step_initialized = 1;
@@ -1342,7 +1346,7 @@ $(function() {
 </script>
 {% endif %}
 
-{% if pp.use_vzero %}
+{% if pp.use_vzero or pp.use_vgs %}
 <style type="text/css">
 .hosted-field {
     height: 2.375em;
@@ -1351,6 +1355,10 @@ $(function() {
     background-color: white;
     border: 1px solid #ccc;
 }
+</style>
+{% endif %}
+{% if pp.use_vzero %}
+<style type="text/css">
 .hosted-field.braintree-hosted-fields-invalid {
     border-color: {{ templateset.custom_fields.color_error|default:"#d00" }};
     background-color: #FFC8C8;
@@ -1493,6 +1501,46 @@ $(function() {
 </script>
 {% endif %}
 
+{% if pp.use_vgs %}
+
+<style>
+  .hosted-field iframe {
+      width:  100%;
+      height: 100%;
+  }
+</style>
+   <script type="text/javascript" src="https://js.verygoodvault.com/vgs-collect/2.8/vgs-collect.js"></script>
+   <script src="/resources/ak_vgs.js"></script>
+   <script>
+     $(function(){
+         var fastActionOptions = {
+             spinnerReplaces: '.ak-field-box', // element temporarily replaced by spinner
+             ccSavePlacement: { before: '.ak-submit-button' },
+             badgePlacement: { before: '.ak-field-box' }, // or after, container
+             // requireComplianceFields: 1, // require employer and occupation
+             vgs: {
+                 form: actionkit.form,
+                 token: '{{ pp.client_token }}',
+                 environment: '{{ pp.vgs_environment }}',
+                 submitOnEmpty: function() { return $('#ak-pay-by-paypal').val() == 1; },
+                 rawFieldSelectors: ["#ak-card_num", "#ak-card_code", "#ak-exp_date-hosted"],
+                 fields: [{ "selector": "#ak-card_num-hosted", "name": "card_num", "type": "card-number", "placeholder": "",
+                           "validations": ["required", "validCardNumberExtended"], "autoComplete": "cc-number",
+                           "color": "black", "successColor": "green", "errorColor": "#d00", "fontSize": "15.5px"},
+                          {"selector": "#ak-card_code-hosted", "name": "card_code", "type": "card-security-code", "placeholder": "",
+                           "validations": ["required", "validCardSecurityCode"], "color": "black",
+                           "successColor": "green", "fontSize": "15.5px", "errorColor": "#d00"}]}};
+         var faForm = new actionkit.forms.fastaction.form(actionkit, fastActionOptions);
+         faForm.init()
+             .then(faForm.processOneclick)
+             .always(function(){
+                 if(faForm.mode == 'interactive'){ // interactive or oneclick failed
+                     faForm.renderInteractive();
+                 }
+             })
+     });
+   </script>
+{% endif %}
 {% endwith %}
 
 {% endblock %}
diff --git a/db_templates/Original/recurring_info.html b/db_templates/Original/recurring_info.html
index d950ceb1b1..3873114748 100644
--- a/db_templates/Original/recurring_info.html
+++ b/db_templates/Original/recurring_info.html
@@ -297,5 +297,27 @@ if (ownership_type_menu.length) {
 }
 </script>
 {% endif %}
+{% if pp.use_vgs %}
+<script>
+   var $akForm = $('#change_profile_{{profile.id}}');
+   var vgsOptions = {form: $akForm[0], token: '{{pp.client_token}}', 
+                     rawFieldSelectors: ['#card_num_{{profile.id}}', '#card_code_{{profile.id}}'],
+                     fields: [{selector: '#' + $akForm[0].id + ' .ak-card_num-hosted',
+                         name: 'card_num', type: 'card-number',
+                         placeholder: '', autoComplete: 'cc-number',
+                         color: 'black', fontSize: '15.5px',
+                         successColor: 'green', errorColor: '#d00'}]};
+   if ($akForm.find('input[name=card_code]').length){
+       vgsOptions.fields.push({selector: '#' + $akForm[0].id + ' .ak-card_code-hosted',
+                  name: 'card_code', type: 'card-security-code',
+                  placeholder: '', fontSize: '15.5px', 
+                  color: 'black', successColor: 'green',
+                  errorColor: '#d00'});
+   }
+   $akForm.find('.ak-card_num-hosted, .ak-card_code-hosted')
+          .addClass('hosted-field');
+   actionkit.donations.vgs.initClient(vgsOptions);
+</script>
 
+{% endif %}
 {% endwith %}
diff --git a/db_templates/Original/recurring_update.html b/db_templates/Original/recurring_update.html
index e9922145af..63332cede9 100644
--- a/db_templates/Original/recurring_update.html
+++ b/db_templates/Original/recurring_update.html
@@ -3,7 +3,7 @@
 
 {% block css_additions %}
 {% for profile in active %}
-{% if profile.payment_processor_information.use_vzero %}
+{% if profile.payment_processor_information.use_vzero or profile.payment_processor_information.use_vgs %}
 {% once %}
 <style type="text/css">
 .hosted-field {
@@ -13,6 +13,11 @@
     border: 1px solid #ccc;
     background-color: white;
 }
+</style>{% endonce %}
+{% endif %}
+{% if profile.payment_processor_information.use_vzero %}
+{% once %}
+<style type="text/css">
 .hosted-field.braintree-hosted-fields-invalid {
     border-color: {{ templateset.custom_fields.color_error|default:"#d00" }};
     background-color: #FFC8C8;
@@ -20,6 +25,13 @@
 </style>
 {% endonce %}
 {% endif %}
+{% if profile.payment_processor_information.use_vgs %}
+  {% once %}
+  <style type="text/css">
+  .hosted-field iframe {width: 100%; height:100%; }
+  </style>
+  {% endonce %}
+{% endif %}
 {% endfor %}
 {% endblock %}
 
@@ -87,7 +99,12 @@ $(function() {
 </script>
 {% endonce %}
 {% endif %}
-
+{% if profile.payment_processor_information.use_vgs %}
+{% once %}
+ <script type="text/javascript" src="https://js.verygoodvault.com/vgs-collect/2.8/vgs-collect.js"></script>
+ <script type="text/javascript" src="/resources/ak_vgs.js"></script>
+{% endonce %}
+{% endif %}
 {% if profile.payment_processor_information.use_vzero %}
 {% once %}
 {% braintree_js_libs %}

2.4.23 (2021 June 21)

Fixed Comma in Event Roster

diff --git a/db_templates/Original/event_roster.html b/db_templates/Original/event_roster.html
index 8bed499241..343de86a72 100644
--- a/db_templates/Original/event_roster.html
+++ b/db_templates/Original/event_roster.html
@@ -31,7 +31,7 @@
             </thead>
             <tbody>
                 {% for signup in signups %}
-                <tr class="{% cycle even,odd %}">
+                <tr class="{% cycle 'even' 'odd' %}">
                     <td class="toggle-col">
                         <input type="checkbox" class="toggle" name="user_id" value="{{ signup.user.id }}">
                     </td>

2.4.22 (2021 June 2)

Corrected Twitter meta tag in wrapper.html

diff --git a/db_templates/Original/wrapper.html b/db_templates/Original/wrapper.html
index cc3a4fb219..a2b8d0f2dc 100644
--- a/db_templates/Original/wrapper.html
+++ b/db_templates/Original/wrapper.html
@@ -14,7 +14,7 @@
     {{ page.followup.share_url_tag }}
     <meta property="og:site_name" content="{% filter ak_text:"org_name" %}{% client_name %}{% endfilter %}">
     <meta property="og:type" content="article">
-    <meta name="twitter:card" value="summary">
+    <meta name="twitter:card" content="summary">
     {% endblock %}
 
     {% block meta_additions %}{% endblock %}

2.4.21 (2021 May 11)

2.4.20 (2021 April 21)

Minor style changes to petition.html

diff --git a/db_templates/Original/petition.html b/db_templates/Original/petition.html
index 03c7d4e990..c406595ac3 100644
--- a/db_templates/Original/petition.html
+++ b/db_templates/Original/petition.html
@@ -24,9 +24,9 @@
             </div>
              {% else %}
                  {% if form.statement_leadin %}
-                    <p class="ak-petition-leadin">
+                    <div class="ak-petition-leadin">
                         {% include_tmpl form.statement_leadin %}
-                    </p>
+                    </div>
                 {% endif %}
                 
                 <div class="ak-margin-bottom-1 ak-text-expander">
@@ -45,9 +45,9 @@
                 
                 {% if page.custom_fields.featured_image or form.about_text %}
                            {% if form.statement_leadin %}
-                            <p class="ak-petition-leadin">
+                            <div class="ak-petition-leadin">
                                 {% include_tmpl form.statement_leadin %}
-                                </p>
+                                </div>
                         {% endif %}
                 
                         <div class="ak-margin-bottom-1">

Change deprecated single equals sign syntax to supported double equals sign syntax

diff --git a/db_templates/Original/event_moderate_tools.html b/db_templates/Original/event_moderate_tools.html
index e4361db73f..69e4ec64f4 100644
--- a/db_templates/Original/event_moderate_tools.html
+++ b/db_templates/Original/event_moderate_tools.html
@@ -125,7 +125,7 @@ $(initModeratorTools);
 		<label for="id_mark_as">Mark event:</label>
 		<select name="event_mark_as" id="id_mark_as">
 		  <option value="">--</option>
-		  {% if event.is_awaiting_approval or event.status = 'flagged' %}
+		  {% if event.is_awaiting_approval or event.status == 'flagged' %}
 		    <option value="approved">Approved</option>
 		  {% endif %}
 		  <option value="flagged">Flagged</option>
@@ -189,7 +189,7 @@ $(initModeratorTools);
                 </td>
                 <td>
                     <p>
-                        {% if action.user = signup.user and not user_is_manager %}
+                        {% if action.user == signup.user and not user_is_manager %}
                           You
                         {% else %}
                           {% if action.user.first_name %}
@@ -295,7 +295,7 @@ $(initModeratorTools);
            {% include "./event_roster.html" %}
         {% endwith %}
         <!-- Guest roster -->
-        {% if attendees or args.form_name = 'manage-attendee' %}
+        {% if attendees or args.form_name == 'manage-attendee' %}
           {% with attendees as signups %}
             {% include "./event_roster.html" %}
           {% endwith %}

2.4.19 (2021 April 1)

(3DS + Account Switcher) Reload page when payment account changes

commit 4660de4c12c02c43a8944234b9d74774ccd2baa4
Author: Timothy Caro-Bruce <tim@wawd.com>
Date:   Fri Mar 12 01:15:03 2021 +0000

    for 3DS + account switcher, reload page when payment account changes

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index f234d55a8f..f7393bbf76 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -1440,7 +1440,51 @@ $(function() {
         actionkit.forms.onValidationErrors(actionkit.errors);
     };
 
+{% if page.use_3d_secure and page.use_account_switcher %}
+    {# get client token from context for appropriate currency #}
+    let braintreeInitialized = false;
+
+    function initFromContext() {
+        if (!braintreeInitialized) {
+            actionkit.donations.initClient(actionkit.context.client_token, options);
+            braintreeInitialized = true;
+        }
+    }
+
+    $(actionkit.form).bind("actionkit.ready", initFromContext);
+    if (actionkit.context) {
+        initFromContext();
+    }
+
+    function reloadWithFormData() {
+        if (actionkit.forms.prefilling) {
+            return;
+        }
+        let qs = new URLSearchParams(window.location.search),
+            fd = new FormData(actionkit.form),
+            skipKeys = {"device_data": 1, "page": 1, "url": 1};
+
+        for (let key of fd.keys()) {
+            if (key in skipKeys) {
+                continue;
+            }
+            let val = fd.get(key);
+            if (val) {
+                qs.set(key, val);
+            } else if (qs.has(key)) {
+                qs.delete(key);
+            }
+        }
+
+        qs.set("prefill", "1");
+
+        window.location.search = qs.toString();
+    }
+    $(":input[name=payment_account]").bind("change", reloadWithFormData);
+
+{% else %}
     actionkit.donations.initClient('{{ pp.client_token }}', options);
+{% endif %}
 
     // read by 3-step validation
     actionkit.donations.skip_cc_validation = true;

Internal changes

diff --git a/db_templates/Original/recurring_info.html b/db_templates/Original/recurring_info.html
index 57514c3a9b..d950ceb1b1 100644
--- a/db_templates/Original/recurring_info.html
+++ b/db_templates/Original/recurring_info.html
@@ -173,7 +173,7 @@
                 </label>
                 <div class="ak-readonly-value">
                     <div class="ak-full-address">{{ profile.user.full_address }}</div>
-                    {% if profile.payment_processor.recurring_update_supports_address %}
+                    {% if pp.recurring_update_supports_address %}
                     <div>
                         <a href="#" onclick="return ak_recurring_change_address('{{ profile.id }}');">Change billing information</a>
                     </div>
@@ -181,7 +181,7 @@
                 </div>
             </div>
     
-            {% if profile.payment_processor.recurring_update_supports_address %}
+            {% if pp.recurring_update_supports_address %}
     
             <div class="ak-change-address" style="display: none">
               {% if not profile.is_ach %}

2.4.18 (2021 March)

3D Secure changes for donate.html

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index 87100be76b..f234d55a8f 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -1394,6 +1394,7 @@ $(function() {
             },
             submit: form.querySelector(".ak-submit-button"),
             {% if page.accept_ach %}ach: true,{% endif %}
+            {% if page.use_3d_secure %}use_3d_secure: true,{% endif %}
         },
         toRemove = ["#ak-card_num", "#ak-card_code", "#ak-exp_date_month",
                     "#ak-exp_date_year", "#ak-card_num-required",

3D Secure changes for recurring_update.html

diff --git a/db_templates/Original/recurring_update.html b/db_templates/Original/recurring_update.html
index 1f811d78af..e9922145af 100644
--- a/db_templates/Original/recurring_update.html
+++ b/db_templates/Original/recurring_update.html
@@ -93,7 +93,7 @@ $(function() {
 {% braintree_js_libs %}
 <script src="/resources/ak_braintree_vzero.js"></script>
 <script>
-function initVZeroForForm(form_id, clientToken, is_ach) {
+function initVZeroForForm(form_id, clientToken, is_ach, use_3d_secure, nonce, bin) {
     var form = document.querySelector(form_id),
         options = {
             form: form,
@@ -124,6 +124,10 @@ function initVZeroForForm(form_id, clientToken, is_ach) {
                 }
             },
             submit: form.querySelector("button[type=submit]"),
+            use_3d_secure: !!use_3d_secure,
+            nonce: nonce,
+            bin: bin,
+            isRecurringUpdate: true,
             // allow empty submit so just amount can be changed
             submitOnEmpty: function() { return true; }
         },

3D Secure changes for recurring_info.html

diff --git a/db_templates/Original/recurring_info.html b/db_templates/Original/recurring_info.html
index 29376a5ab0..57514c3a9b 100644
--- a/db_templates/Original/recurring_info.html
+++ b/db_templates/Original/recurring_info.html
@@ -283,7 +283,7 @@
 </form>
 {% if pp.use_vzero %}
 <script>
-initVZeroForForm('#change_profile_{{ profile.id }}', '{{ pp.client_token }}', {% if profile.is_ach %}true{% else %}false{% endif %});
+initVZeroForForm('#change_profile_{{ profile.id }}', '{{ pp.client_token }}', {% if profile.is_ach %}true{% else %}false{% endif %}, {% if pp.use_3d_secure %}true, {% using profile.payment_nonce_info %}'{{nonce}}', '{{bin}}'{% endusing %}{% else %}false, '', ''{% endif %});
 
 var ownership_type_menu = $('#change_profile_{{ profile.id }} [name=ownership_type]');
 if (ownership_type_menu.length) {

2.4.13 (2020 September)

Remove unnecessary {% load actionkit_tags %} instances; these tags are loaded automatically.

diff --git a/db_templates/Original/call.html b/db_templates/Original/call.html
index 274da44ce0..dab43c63e5 100644
--- a/db_templates/Original/call.html
+++ b/db_templates/Original/call.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 {% block content %}
 
 <div class="ak-grid-row">
diff --git a/db_templates/Original/country_select.html b/db_templates/Original/country_select.html
index 5efb407b11..93ba8c3d13 100644
--- a/db_templates/Original/country_select.html
+++ b/db_templates/Original/country_select.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 
 <select name="{{ input_name_prefix }}country" id="id_{{ input_name_prefix }}country" {% if onchange %}onchange="{{ onchange }}" onblur="{{ onchange }}"{% endif %}>
     {% if page.lang_or_en.country_names_us_first %}
diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index ceb2e14f58..87100be76b 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 {% block body_extra_classes %}{% if page.payment_processor_information.is_quickdonate %}actionkit-donate-qd {% endif %}{{ block.super }}{% endblock %}
 {% block content %}
 <form id="act" name="act" class="{{ templateset.custom_fields.donation_steps }}" method="POST" action="/act/" accept-charset="utf-8">
diff --git a/db_templates/Original/event_attend.html b/db_templates/Original/event_attend.html
index 871b3db3d4..d98ea137bc 100644
--- a/db_templates/Original/event_attend.html
+++ b/db_templates/Original/event_attend.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block css_additions %}
 	<link href="/resources/leaflet/leaflet.css" rel="stylesheet" type="text/css">
diff --git a/db_templates/Original/event_attendee_details.html b/db_templates/Original/event_attendee_details.html
index d09becd35e..e791ea8539 100644
--- a/db_templates/Original/event_attendee_details.html
+++ b/db_templates/Original/event_attendee_details.html
@@ -1,5 +1,3 @@
-{% load actionkit_tags %}
-
 {% filter collapse_spaces %}
 
 <div class="ak-field-box">
diff --git a/db_templates/Original/event_attendee_taf_msg.txt b/db_templates/Original/event_attendee_taf_msg.txt
index 23e56f61bd..3508d607f6 100644
--- a/db_templates/Original/event_attendee_taf_msg.txt
+++ b/db_templates/Original/event_attendee_taf_msg.txt
@@ -1,5 +1,4 @@
 Subject: Hope you can come!
-{% load actionkit_tags %}
 {% with action.event as event %}
 Hi,
 
diff --git a/db_templates/Original/event_attendee_tools.html b/db_templates/Original/event_attendee_tools.html
index 99ff16e52f..7bd6561fb8 100644
--- a/db_templates/Original/event_attendee_tools.html
+++ b/db_templates/Original/event_attendee_tools.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 
 {% block content %}
 {% if signup and not event.is_inactive %}
diff --git a/db_templates/Original/event_contact.html b/db_templates/Original/event_contact.html
index 5cefd54c64..f80e03b361 100644
--- a/db_templates/Original/event_contact.html
+++ b/db_templates/Original/event_contact.html
@@ -1,5 +1,3 @@
-{% load actionkit_tags %}
-
 <a name="contact{% if to %}-{{to}}{% endif %}"></a>
 
 <div class="ak-nodisplay-if-js contact-form contact{% if to %}-{{to}}{% endif %} ak-field-box ak-margin-1">
diff --git a/db_templates/Original/event_create.html b/db_templates/Original/event_create.html
index a28ceccfcd..c2102dfd92 100644
--- a/db_templates/Original/event_create.html
+++ b/db_templates/Original/event_create.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block content %}
 
diff --git a/db_templates/Original/event_created.html b/db_templates/Original/event_created.html
index afa51799d1..ecefe35fa2 100644
--- a/db_templates/Original/event_created.html
+++ b/db_templates/Original/event_created.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block content %}
 <div class="ak-grid-row">
diff --git a/db_templates/Original/event_email_approved.html b/db_templates/Original/event_email_approved.html
index 28418967d3..fb4c0cb732 100644
--- a/db_templates/Original/event_email_approved.html
+++ b/db_templates/Original/event_email_approved.html
@@ -1,6 +1,5 @@
 Subject: Your "{{campaign_title}}" event has been approved!
 
-{% load actionkit_tags %}
 <!-- If a campaign requires event approval, this email gets sent out to event hosts when their event is approved -->
 
 <p>Congratulations! Your "{{campaign_title}}" event has been approved and is now open for signups!</p>
diff --git a/db_templates/Original/event_email_attendee_removed.html b/db_templates/Original/event_email_attendee_removed.html
index a9eea0a6df..0adfe9f0bf 100644
--- a/db_templates/Original/event_email_attendee_removed.html
+++ b/db_templates/Original/event_email_attendee_removed.html
@@ -1,6 +1,5 @@
 Subject: You have been removed from your "{{campaign_title}}" event
 
-{% load actionkit_tags %}
 <p>You have been removed from the "{{ campaign.local_title }}" event you were signed up to attend.</p>
 
 <!-- Replacement for unsubscribe. -->
diff --git a/db_templates/Original/event_email_cancelled.html b/db_templates/Original/event_email_cancelled.html
index 6cc5c73351..027b8ff022 100644
--- a/db_templates/Original/event_email_cancelled.html
+++ b/db_templates/Original/event_email_cancelled.html
@@ -1,6 +1,5 @@
 Subject: Your "{{campaign_title}}" event has been cancelled
 
-{% load actionkit_tags %}
 <p>The "{{ campaign.local_title }}" event you signed up for has been cancelled.</p>
 
 <p>You can search for other events here:</p>
diff --git a/db_templates/Original/event_email_created.html b/db_templates/Original/event_email_created.html
index c2ad4c2085..b6606b8abe 100644
--- a/db_templates/Original/event_email_created.html
+++ b/db_templates/Original/event_email_created.html
@@ -1,6 +1,5 @@
 Subject: {% if action.event.campaign.require_email_confirmation %}Confirm{% else %}Manage{% endif %} your "{{ action.event.campaign.local_title }}" event
 
-{% load actionkit_tags %}
 <!-- event_email_created.html in the templateset contains the default
      confirmation email for hosts -->
 
diff --git a/db_templates/Original/event_email_details_changed.html b/db_templates/Original/event_email_details_changed.html
index 7d303b9bc3..29efd24a89 100644
--- a/db_templates/Original/event_email_details_changed.html
+++ b/db_templates/Original/event_email_details_changed.html
@@ -1,7 +1,5 @@
 Subject: Event details changed for your "{{ event.title }}" event
 
-{% load actionkit_tags %}
-
 This is a notification that an event you're signed up for has been changed:
 {% for field, change in changes.items %}
 <li><b>{{ field }}</b> changed to {{ change.1 }}</li>
diff --git a/db_templates/Original/event_email_from_admin.html b/db_templates/Original/event_email_from_admin.html
index cfbae87e3c..80089594cc 100644
--- a/db_templates/Original/event_email_from_admin.html
+++ b/db_templates/Original/event_email_from_admin.html
@@ -1,6 +1,5 @@
 Subject: {{subject}}
 
-{% load actionkit_tags %}
 <p>{{ message|linebreaksbr }}</p>
 
 <hr>
diff --git a/db_templates/Original/event_email_from_attendee.html b/db_templates/Original/event_email_from_attendee.html
index 63feecfbd1..702196f36b 100644
--- a/db_templates/Original/event_email_from_attendee.html
+++ b/db_templates/Original/event_email_from_attendee.html
@@ -1,7 +1,5 @@
 Subject: Your "{{campaign_title}}" attendee sent you a message
 
-{% load actionkit_tags %}
-
 <p>Here's a message from {{ sender_signup.user }}, an {{ sender_signup.role }} of your {{ campaign.local_title }} event:</p>
 
 <blockquote>
diff --git a/db_templates/Original/event_email_from_host.html b/db_templates/Original/event_email_from_host.html
index 4bc2df4ffc..beb8d07128 100644
--- a/db_templates/Original/event_email_from_host.html
+++ b/db_templates/Original/event_email_from_host.html
@@ -1,7 +1,5 @@
 Subject: {{subject}}
 
-{% load actionkit_tags %}
-
 <p>Here's a message from {{ sender_signup.user }}, a {{ sender_signup.role }} of your {{ campaign.local_title }} event:</p>
 
 <blockquote>
diff --git a/db_templates/Original/event_email_from_moderator.html b/db_templates/Original/event_email_from_moderator.html
index ea2fedc7f7..1e27ba8bb5 100644
--- a/db_templates/Original/event_email_from_moderator.html
+++ b/db_templates/Original/event_email_from_moderator.html
@@ -1,7 +1,5 @@
 Subject: {{subject}}
 
-{% load actionkit_tags %}
-
 <p>Here's a message from {{ sender_signup.user }}, a moderator of your {{ campaign.local_title }} event:</p>
 <p>{{ message|linebreaksbr }}</p>
 
diff --git a/db_templates/Original/event_email_role_changed.html b/db_templates/Original/event_email_role_changed.html
index 28abeda205..e5a6b52655 100644
--- a/db_templates/Original/event_email_role_changed.html
+++ b/db_templates/Original/event_email_role_changed.html
@@ -1,6 +1,5 @@
 Subject: You are now a {{new_role}} of your "{{campaign_title}}" event
 
-{% load actionkit_tags %}
 <p>You are now a {{ signup.role }} of your "{{ campaign.local_title }}" event.</p>
 
 <p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}">https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}</a></p>
diff --git a/db_templates/Original/event_email_volunteer_approved.html b/db_templates/Original/event_email_volunteer_approved.html
index c0971c369f..8ba3990017 100644
--- a/db_templates/Original/event_email_volunteer_approved.html
+++ b/db_templates/Original/event_email_volunteer_approved.html
@@ -1,6 +1,5 @@
 Subject: You have been approved to moderate "{{campaign_title}}"!
 
-{% load actionkit_tags %}
 <!-- This email is sent to campaign volunteers once they are approved -->
 
 <p>Your offer to moderate "{{campaign_title}}" has been approved and you're ready to get started.</p>
diff --git a/db_templates/Original/event_host_details.html b/db_templates/Original/event_host_details.html
index 46aa5a25df..5e80dacdb7 100644
--- a/db_templates/Original/event_host_details.html
+++ b/db_templates/Original/event_host_details.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 {% filter collapse_spaces %}
 
 {% if event.venue %}
diff --git a/db_templates/Original/event_host_taf_msg.txt b/db_templates/Original/event_host_taf_msg.txt
index 964267a97b..09869c5886 100644
--- a/db_templates/Original/event_host_taf_msg.txt
+++ b/db_templates/Original/event_host_taf_msg.txt
@@ -1,5 +1,4 @@
 Subject: Hope you can come!
-{% load actionkit_tags %}
 {% with action.event as event %}
 Hi,
 
diff --git a/db_templates/Original/event_host_tools.html b/db_templates/Original/event_host_tools.html
index 9a9fc2f3ab..1beb52b286 100644
--- a/db_templates/Original/event_host_tools.html
+++ b/db_templates/Original/event_host_tools.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block script_additions %}
 
diff --git a/db_templates/Original/event_invite.html b/db_templates/Original/event_invite.html
index 36a83c1fc5..fdf36b6f88 100644
--- a/db_templates/Original/event_invite.html
+++ b/db_templates/Original/event_invite.html
@@ -1,5 +1,3 @@
-{% load actionkit_tags %}
-
 {% if page.pagefollowup.send_taf %}
 
     <a name="invite-friends"></a>
diff --git a/db_templates/Original/event_moderate_details.html b/db_templates/Original/event_moderate_details.html
index ae2246386f..72ac66d5ce 100644
--- a/db_templates/Original/event_moderate_details.html
+++ b/db_templates/Original/event_moderate_details.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 {% filter collapse_spaces %}
 
 {% if event.venue %}
diff --git a/db_templates/Original/event_roster.html b/db_templates/Original/event_roster.html
index fb0fa1bb36..8bed499241 100644
--- a/db_templates/Original/event_roster.html
+++ b/db_templates/Original/event_roster.html
@@ -1,5 +1,3 @@
-{% load actionkit_tags %}
-
 <!-- event_roster.html: Cohost/attendee roster for host page -->
 <style type="text/css">
     .{{signups.role}}-list .toggle-col { width: 1% }
diff --git a/db_templates/Original/event_roster_add.html b/db_templates/Original/event_roster_add.html
index 23dc650cc9..1abe031080 100644
--- a/db_templates/Original/event_roster_add.html
+++ b/db_templates/Original/event_roster_add.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 <!-- event_roster_add.html: add attendees for host page -->
 <style>
   #more-attendees {display: none}
diff --git a/db_templates/Original/event_search.html b/db_templates/Original/event_search.html
index 8db7e234a4..e8f4f29e9a 100644
--- a/db_templates/Original/event_search.html
+++ b/db_templates/Original/event_search.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 
 {% block css_additions %}
 	<link href="/resources/leaflet/leaflet.css" rel="stylesheet" type="text/css">
diff --git a/db_templates/Original/event_search_moderator.html b/db_templates/Original/event_search_moderator.html
index 23cc973eb7..abb2b36e6a 100644
--- a/db_templates/Original/event_search_moderator.html
+++ b/db_templates/Original/event_search_moderator.html
@@ -1,5 +1,5 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags humanize %}
+{% load humanize %}
 
 {% block script_additions %}
 <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.js"></script>
diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index b99a05f199..f88fa3c725 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 {% filter collapse_spaces %}
 
 {% comment %}
@@ -202,4 +201,4 @@
     </ul>
 {% endif %}
 
-{% endfilter %}
\ No newline at end of file
+{% endfilter %}
diff --git a/db_templates/Original/event_volunteer.html b/db_templates/Original/event_volunteer.html
index 251e548f24..168f8c18fe 100644
--- a/db_templates/Original/event_volunteer.html
+++ b/db_templates/Original/event_volunteer.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 
 {% block content %}
 
diff --git a/db_templates/Original/inline_tellafriend.html b/db_templates/Original/inline_tellafriend.html
index 55fda4e03c..c96bf2446b 100644
--- a/db_templates/Original/inline_tellafriend.html
+++ b/db_templates/Original/inline_tellafriend.html
@@ -1,5 +1,3 @@
-{% load actionkit_tags %}
-
 {% if page.followup.send_taf %}
 
     <div class="ak-inline-taf">
diff --git a/db_templates/Original/letter.html b/db_templates/Original/letter.html
index 54e65e5bb2..af4313a959 100644
--- a/db_templates/Original/letter.html
+++ b/db_templates/Original/letter.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 
 {% block content %}
 
diff --git a/db_templates/Original/lte.html b/db_templates/Original/lte.html
index f7f6806035..da3146fe7c 100644
--- a/db_templates/Original/lte.html
+++ b/db_templates/Original/lte.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block script_additions %}
 
diff --git a/db_templates/Original/password.html b/db_templates/Original/password.html
index 94c2021acd..a108235b7a 100644
--- a/db_templates/Original/password.html
+++ b/db_templates/Original/password.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block content %}
 
diff --git a/db_templates/Original/petition.html b/db_templates/Original/petition.html
index 3520e3ea94..03c7d4e990 100644
--- a/db_templates/Original/petition.html
+++ b/db_templates/Original/petition.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 
 {% block content %}
 
diff --git a/db_templates/Original/privacy.html b/db_templates/Original/privacy.html
index 3f6066e5a0..0e9fb8dfca 100644
--- a/db_templates/Original/privacy.html
+++ b/db_templates/Original/privacy.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 {% filter collapse_spaces %}
 {% with pt=privacy_text %}
 <div class="ak-privacy ak-errs-below">
@@ -52,4 +51,4 @@
     
 </div>
 {% endwith %}
-{% endfilter %}
\ No newline at end of file
+{% endfilter %}
diff --git a/db_templates/Original/recurring_cancel.html b/db_templates/Original/recurring_cancel.html
index e6114cfe1f..c9619af826 100644
--- a/db_templates/Original/recurring_cancel.html
+++ b/db_templates/Original/recurring_cancel.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 
 {% block content %}
 
diff --git a/db_templates/Original/recurring_update.html b/db_templates/Original/recurring_update.html
index 539d00cca1..1f811d78af 100644
--- a/db_templates/Original/recurring_update.html
+++ b/db_templates/Original/recurring_update.html
@@ -1,5 +1,5 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags humanize %}
+{% load humanize %}
 
 {% block css_additions %}
 {% for profile in active %}
diff --git a/db_templates/Original/reset.html b/db_templates/Original/reset.html
index 526eb18475..ce7d1023d8 100644
--- a/db_templates/Original/reset.html
+++ b/db_templates/Original/reset.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block content %}
 
diff --git a/db_templates/Original/reverse_donation.html b/db_templates/Original/reverse_donation.html
index 96c4861828..e182faab0d 100644
--- a/db_templates/Original/reverse_donation.html
+++ b/db_templates/Original/reverse_donation.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 {% block content %}
 <h2>Hello, {{ user.first_name }}</h2>
 <h3>Reverse donation</h3>
diff --git a/db_templates/Original/signup.html b/db_templates/Original/signup.html
index e2def964fe..ca7b3625f8 100644
--- a/db_templates/Original/signup.html
+++ b/db_templates/Original/signup.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 
 {% block content %}
 
diff --git a/db_templates/Original/site_root.html b/db_templates/Original/site_root.html
index b683886aea..53a7f24295 100644
--- a/db_templates/Original/site_root.html
+++ b/db_templates/Original/site_root.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 <html>
 <head>
 </head>
diff --git a/db_templates/Original/social_plugins.html b/db_templates/Original/social_plugins.html
index 756acf5c48..7b3f1801c9 100644
--- a/db_templates/Original/social_plugins.html
+++ b/db_templates/Original/social_plugins.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 <div class="social-links">
     <iframe class="facebook-share-button" src="//www.facebook.com/plugins/like.php?href={{page.canonical_url}}&amp;send=false&amp;layout=button_count&amp;show_faces=false&amp;action=like&amp;colorscheme=light&amp;font&amp;appId={% facebook_app %}" scrolling="no" frameborder="0" allowTransparency="true"></iframe>
     <a href="https://twitter.com/share" class="twitter-share-button" data-url="{{ page.canonical_url }}" data-text="{{ page.title }}">Tweet</a>
diff --git a/db_templates/Original/survey.html b/db_templates/Original/survey.html
index 5f6ddd71df..a5b694e4f9 100644
--- a/db_templates/Original/survey.html
+++ b/db_templates/Original/survey.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block content %}
 
diff --git a/db_templates/Original/thanks.html b/db_templates/Original/thanks.html
index 2a87852759..9895bafc3b 100644
--- a/db_templates/Original/thanks.html
+++ b/db_templates/Original/thanks.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block content %}
 
diff --git a/db_templates/Original/unsubscribe.html b/db_templates/Original/unsubscribe.html
index e8c3c9fef2..a8dc031299 100644
--- a/db_templates/Original/unsubscribe.html
+++ b/db_templates/Original/unsubscribe.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block content %}
 
diff --git a/db_templates/Original/user_form.html b/db_templates/Original/user_form.html
index c07ab0720f..d1eae76ba3 100644
--- a/db_templates/Original/user_form.html
+++ b/db_templates/Original/user_form.html
@@ -1,4 +1,4 @@
-{% load actionkit_tags switchcase %}
+{% load switchcase %}
 {% filter remove_blank_lines %}
 
 {% comment %}
diff --git a/db_templates/Original/user_update.html b/db_templates/Original/user_update.html
index 5028da6002..e1fda0d8fc 100644
--- a/db_templates/Original/user_update.html
+++ b/db_templates/Original/user_update.html
@@ -1,6 +1,5 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
-{% load actionkit_tags switchcase %}
+{% load switchcase %}
 
 {% block title %}Update Your Account{% endblock %}
 
diff --git a/db_templates/Original/verify_donor.html b/db_templates/Original/verify_donor.html
index bcdfc96fc8..4b67547e52 100644
--- a/db_templates/Original/verify_donor.html
+++ b/db_templates/Original/verify_donor.html
@@ -1,4 +1,4 @@
-{% extends "./wrapper.html" %}{% load actionkit_tags %}
+{% extends "./wrapper.html" %}
 {% block content %}
 <h2>Hello, {{ user.first_name }}</h2>
 <p>
diff --git a/db_templates/Original/whipcount.html b/db_templates/Original/whipcount.html
index 43dba8ade1..ac54998db0 100644
--- a/db_templates/Original/whipcount.html
+++ b/db_templates/Original/whipcount.html
@@ -1,5 +1,4 @@
 {% extends "./wrapper.html" %}
-{% load actionkit_tags %}
 
 {% block content %}
 
diff --git a/db_templates/Original/wrapper.html b/db_templates/Original/wrapper.html
index b6bf825296..cc3a4fb219 100644
--- a/db_templates/Original/wrapper.html
+++ b/db_templates/Original/wrapper.html
@@ -1,4 +1,3 @@
-{% load actionkit_tags %}
 <!DOCTYPE html>
 <html lang="{{page.lang.iso_code|default:'en'}}"{% if page.lang.is_rtl %} dir="rtl"{% endif %}>
 <head>

Event moderation form display time zone for global events.

diff --git a/db_templates/Original/event_moderate_details.html b/db_templates/Original/event_moderate_details.html
index 72ac66d5ce..a211520e79 100644
--- a/db_templates/Original/event_moderate_details.html
+++ b/db_templates/Original/event_moderate_details.html
@@ -17,7 +17,7 @@
     {% if event.get_starts_at_display %}
     <tr class="ak-event-time">
         <th>When:</th>
-        <td>{{ event.get_starts_at_display }}{% if event.is_in_past %} <span class="ak-error ak-event-over-notice">(event is now over)</span>{% endif %}</td>
+        <td>{{ event.get_starts_at_display }} {% if event.show_timezone %}{{ event.local_timezone|timezone_display }}{% endif %} {% if event.is_in_past %} <span class="ak-error ak-event-over-notice">(event is now over)</span>{% endif %}</td>
     </tr>
     {% endif %}
     {% if event.directions %}

2.4.12 (2020 August 19)

Event time zone display

diff --git a/db_templates/Original/event_attendee_details.html b/db_templates/Original/event_attendee_details.html
index db1340e05e..d09becd35e 100644
--- a/db_templates/Original/event_attendee_details.html
+++ b/db_templates/Original/event_attendee_details.html
@@ -23,7 +23,7 @@
             <th>When:</th>
             <td>
                 {{ event.get_starts_at_display }}
-                {% if event.show_timezone %}{{ event.local_timezone }} time{% endif %}
+                {% if event.show_timezone %}{{ event.local_timezone|timezone_display }}{% endif %}
                 {% if event.is_in_past %}
                     <span class="ak-error ak-event-over-notice">
                         (event is now over)
diff --git a/db_templates/Original/event_attendee_taf_msg.txt b/db_templates/Original/event_attendee_taf_msg.txt
index 40200af944..23e56f61bd 100644
--- a/db_templates/Original/event_attendee_taf_msg.txt
+++ b/db_templates/Original/event_attendee_taf_msg.txt
@@ -8,7 +8,7 @@ I'm attending an event as part of {% client_name %}'s
 "{{ event.campaign.local_title }}" campaign.  It's 
 {% if event.mode == "onsite" %}at {{ event.address1 }} {% if event.venue %}({{ event.venue }}){% endif %}{% else %}{% if event.venue %}on {{ event.venue }}{% else %}online{% endif %}{% endif %}
 {% if event.mode != "global" %}in {% if event.search_show_map %}{{ event.city }}, {% endif %}{{ event.region }}{% endif %} 
-on {{ event.starts_at|date:"l, F j" }} at {{ event.starts_at|date:"f A" }}{% if event.mode == "global" %} {{ event.local_timezone }}{% endif %}.
+on {{ event.starts_at|date:"l, F j" }} at {{ event.starts_at|date:"f A" }}{% if event.show_timezone %} {{ event.local_timezone|timezone_display }}{% endif %}.
 {% endfilter %}
 
 RSVP here to join me:
diff --git a/db_templates/Original/event_host_details.html b/db_templates/Original/event_host_details.html
index cc067f2f93..46aa5a25df 100644
--- a/db_templates/Original/event_host_details.html
+++ b/db_templates/Original/event_host_details.html
@@ -25,7 +25,7 @@
         <th>When:</th>
         <td>
             {{ event.get_starts_at_display }}
-            {% if event.show_timezone %}{{ event.local_timezone }} time{% endif %}
+            {% if event.show_timezone %}{{ event.local_timezone|timezone_display }}{% endif %}
             {% if event.is_in_past %}<span class="ak-error ak-event-over-notice">(event is now over)</span>{% endif %}
         </td>
     </tr>
diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index 2ba0675ef5..21fc4ed52a 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -144,7 +144,7 @@
 						{% if event.starts_at %}
 							<tr class="ak-event-time">
 								<th>When:</th>
-								<td>{{ event.starts_at_dt|date:"l, F jS, G:i a" }} {% if "Global" in event.mode %}{{ event.local_timezone }} time{% endif %}</td>
+								<td>{{ event.starts_at_dt|date:"l, F jS, G:i a" }} {% if "Global" in event.mode %}{{ event.local_timezone|timezone_display }}{% endif %}</td>
 							</tr>
 						{% endif %}
 						{% if event.campaign.show_directions and event.directions %}

Added region field to event create form

diff --git a/db_templates/Original/event_create.html b/db_templates/Original/event_create.html
index fb95881c08..a28ceccfcd 100644
--- a/db_templates/Original/event_create.html
+++ b/db_templates/Original/event_create.html
@@ -163,6 +163,10 @@
                             <label for="id_event_state">State</label>
                             {% include "./state_select.html" %}
                         </div>
+                        <div>
+                            <label for="id_event_region">Region</label>
+                            <input id="id_event_region" type="text" name="event_region">
+                        </div>
                         <div>
                             <label for="id_event_postal">Postal</label>
                             <input id="id_event_postal" type="text" name="event_postal">
@@ -326,6 +330,7 @@
         var is_us = isEventUnitedStates();
         var event_zip = actionkit.form.event_zip,
         event_postal = actionkit.form.event_postal,
+        event_region = actionkit.form.event_region,
         event_state = actionkit.form.event_state;
         if ( event_zip && event_postal ) {
             if ( is_us ) actionkit.forms.showAndHide(event_zip, event_postal);
@@ -345,6 +350,17 @@
                 hiddenRow.hide();
             }
         }
+        if ( event_region ) {
+            if ( ! is_us ) {
+                $(event_region).removeAttr('disabled');
+                actionkit.forms.getRowForElement(event_region).show();
+            }
+            else {
+                $(event_region).attr('disabled', true);
+                $(event_region).val('').trigger('change');
+                actionkit.forms.getRowForElement(event_region).hide();
+            }
+        }
     };
     
     reflectEventMode = function () {
@@ -377,14 +393,7 @@
 
     $(document).ready( function() {
         var prev_is_us, now_is_us;
-        $(actionkit.form.event_country).focus(function() {
-            prev_is_us = isEventUnitedStates();
-        }).change(function() {
-            now_is_us = isEventUnitedStates();
-            if ( prev_is_us != now_is_us ) {
-                reflectEventCountryChange();
-            }
-        });
+        $(actionkit.form.event_country).on('change', reflectEventCountryChange);
         $('.ak-signoff-box input[type="checkbox"]').on('change', function() {
             if ($(this).is(':checked')) {
                 $(this).next('label.ak-checkbox-label').removeClass('ak-error');

Speed up map display by adding markers in bulk

diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index c2f20126fa..38f68dbb47 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -98,10 +98,16 @@
 					})
 				}; //icon
 
-				for(var i=0; i<loc.length; i++) {
-					markers.addLayer(L.marker([loc[i][1], loc[i][2]],{icon: icon[loc[i][3]]} ).bindPopup(loc[i][0]));
-					map.addLayer(markers);
-				}
+                var marker_items = [];
+                for(var i=0; i<loc.length; i++) {
+                    marker_items.push(
+                        L.marker(
+                            [loc[i][1], loc[i][2]], {icon: icon[loc[i][3]]}
+                        ).bindPopup(loc[i][0])
+                    );
+                }
+                markers.addLayers(marker_items);
+                map.addLayer(markers);
 			   </script>
     	
     		   <div id="map"></div>

Send map pin information in HTML, not JavaScript

diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index 38f68dbb47..161b2a1ffc 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -32,35 +32,38 @@
 				.leaflet-container a.ak-button { color: #fff; }
 				#map .leaflet-popup-content p { margin: 5px 0px; }
 			</style>
-			<script>
-				/*List to hold events*/
-				var loc = [];
-				{% for event in map_events %}
-					loc.push([ 
-							{% if event.is_in_past or event.is_full %}
-								'<p>{{ event.starts_at_dt|date:"l, F jS, g:i a" }}</p>' +
-								'<p class="ak-event-title"><a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}"' +
-								{% if event.public_title_is_from_campaign %}
-								' class="ak-campaign-title">' + 
-								{% endif %}
-								'{{ event.public_title }}</a></p>' +
-								'<p>{% if event.search_show_address1 %}{{ event.address1 }}<br>{% endif %}{{ event.search_general_location }}</p>' +
-								'{% if event.is_in_past %} <p><strong>Sorry, the event is over.</strong></p> {% endif %} {% if event.is_full %} <p><strong>Sorry, the event is full.</strong></p> {% endif %}'
-							{% else %}
-								'<p>{{ event.starts_at_dt|date:"l, F jS, g:i a" }}</p>' +
-								'<p class="ak-event-title"><a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}"' +
-								{% if event.public_title_is_from_campaign %}
-								' class="ak-campaign-title">' + 
-								{% endif %}
-								'{{ event.public_title }}</a></p>' +
-								'<p>{% if event.search_show_address1 %}{{ event.address1 }}<br>{% endif %}{{ event.search_general_location }}</p>' +
-								'<a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}" class="ak-button">Sign up</a>'
-							{% endif %},
-							{{ event.latitude }},
-							{{ event.longitude }},
-							{% if event.is_open_for_signup %} 'open_for_signup' {% else %} 'not_open_for_signup' {% endif %}
-							]);
-				{% endfor %}
+
+            <div id="map-items" style="display: none">
+                {% for event in map_events %}
+                    <div data-latitude="{{ event.latitude }}" data-longitude="{{ event.longitude }}" data-icon="{% if event.is_open_for_signup %}open_for_signup{% else %}not_open_for_signup{% endif %}">
+                        <p>{{ event.starts_at_dt|date:"l, F jS, g:i a" }}</p>
+                        <p class="ak-event-title">
+                            <a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}" {% if event.public_title_is_from_campaign %}class="ak-campaign-title"{% endif %}>{{ event.public_title }}</a>
+                        </p>
+                        <p>{% if event.search_show_address1 %}{{ event.address1 }}<br>{% endif %}{{ event.search_general_location }}</p>
+                        {% if event.is_in_past %}
+                            <p><strong>Sorry, the event is over.</strong></p>
+                        {% elif event.is_full %}
+                            <p><strong>Sorry, the event is full.</strong></p>
+                        {% else %}
+                            <a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}" class="ak-button">Sign up</a>
+                        {% endif %}
+                    </div>
+                {% endfor %}
+            </div>
+
+            <script>
+                // Extract list of mappable events
+                var loc = [];
+                $('#map-items div').each( function () {
+                    var item = $( this );
+                    loc.push( [
+                        item.html(),
+                        item.data('latitude'),
+                        item.data('longitude'),
+                        item.data('icon')
+                    ] );
+                } ).remove();
 				
 				{% if all and not args.akid %}
 				/* akids in precooked html may be empty because of server-side cache */

Fix an incorrect use of 24-hour time with am/pm

diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index 160f3244e7..c2f20126fa 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -38,7 +38,7 @@
                                {% for event in map_events %}
                                        loc.push([ 
                                                        {% if event.is_in_past or event.is_full %}
-                                                               '<p>{{ event.starts_at_dt|date:"l, F jS, G:i a" }}</p>' +
+                                                               '<p>{{ event.starts_at_dt|date:"l, F jS, g:i a" }}</p>' +
                                                                '<p class="ak-event-title"><a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}"' +
                                                                {% if event.public_title_is_from_campaign %}
                                                                ' class="ak-campaign-title">' + 
@@ -47,7 +47,7 @@
                                                                '<p>{% if event.search_show_address1 %}{{ event.address1 }}<br>{% endif %}{{ event.search_general_location }}</p>' +
                                                                '{% if event.is_in_past %} <p><strong>Sorry, the event is over.</strong></p> {% endif %} {% if event.is_full %} <p><strong>Sorry, the event is full.</strong></p> {% endif %}'
                                                        {% else %}
-                                                               '<p>{{ event.starts_at_dt|date:"l, F jS, G:i a" }}</p>' +
+                                                               '<p>{{ event.starts_at_dt|date:"l, F jS, g:i a" }}</p>' +
                                                                '<p class="ak-event-title"><a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}"' +
                                                                {% if event.public_title_is_from_campaign %}
                                                                ' class="ak-campaign-title">' + 

Fix second incorrect use of 24-hour time with am/pm

diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index 160f3244e7..c2f20126fa 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -138,7 +138,7 @@
                                                {% if event.starts_at %}
                                                        <tr class="ak-event-time">
                                                                <th>When:</th>
-                                                               <td>{{ event.starts_at_dt|date:"l, F jS, G:i a" }} {% if "Global" in event.mode %}{{ event.local_timezone|timezone_display }}{% endif %}</td>
+                                                               <td>{{ event.starts_at_dt|date:"l, F jS, g:i a" }} {% if "Global" in event.mode %}{{ event.local_timezone|timezone_display }}{% endif %}</td>
                                                        </tr>
                                                {% endif %}
                                                {% if event.campaign.show_directions and event.directions %}

2.4.11 (2020 July 21)

Event moderation advanced search toggle

diff --git a/db_templates/Original/event_search_moderator.html b/db_templates/Original/event_search_moderator.html
index 4e978df86d..23cc973eb7 100644
--- a/db_templates/Original/event_search_moderator.html
+++ b/db_templates/Original/event_search_moderator.html
@@ -11,7 +11,7 @@
         $("#event-count").closest("div").hide();
     });
     $(".ak-advanced-toggle").click(function(e){
-        $(e.target).toggleClass("ak-collapse-toggle-inactive");
+        $(".ak-advanced-toggle").toggleClass("ak-collapse-toggle-inactive");
         $(".ak-advanced").toggle("fast");
     });
 

2.4.10 (2020 June 15)

Support virtual events

diff --git a/db_templates/Original/event_attend.html b/db_templates/Original/event_attend.html
index d3d5b0925f..56c37bf985 100644
--- a/db_templates/Original/event_attend.html
+++ b/db_templates/Original/event_attend.html
@@ -115,12 +115,20 @@ $(window).on('load', function (){
                     <!-- ...to here -->
                 </div>
                 
-                {% if campaign.show_address1 %}
+                {% if event.search_show_map %}
                 <!--<img src="http://maps.google.com/maps/api/staticmap?sensor=false&size=500x300&markers={{ event.address1|urlencode }}, {{ event.city_etc|urlencode }}">-->
+                {% if not event.is_virtual %}
                 <h5><a href="https://www.google.com/maps/place/{{ event.address1|urlencode }},%20{{ event.city_etc|urlencode }}" target="_blank" class="ak-larger-text"><small><img src="/media/modern/blue_marker.png" class="ak-marker"/> Click to see larger</a></small></h5>
+                {% endif %}
                 <div id="ak-map"></div>
                 
-                <p class="caption">Sign up to get driving directions and more.</p>
+                <p class="caption">
+                    {% if event.is_virtual %}
+                        Sign up to get event updates and more.
+                    {% else %}
+                        Sign up to get driving directions and more.
+                    {% endif %}
+                </p>
 
                 {% endif %}
 
diff --git a/db_templates/Original/event_attendee_details.html b/db_templates/Original/event_attendee_details.html
index 0db6d3eb1c..9c7e822a83 100644
--- a/db_templates/Original/event_attendee_details.html
+++ b/db_templates/Original/event_attendee_details.html
@@ -12,8 +12,10 @@
         <span class="ak-event-venue">{{ event.venue }}</span>
     {% endif %}
 
+    {% if event.attendee_show_address1 %}
     <div class="ak-event-address1">{{ event.address1 }}</div>
-    <div class="ak-event-city-etc">{{ event.city_etc }}</div>
+    {% endif %}
+    <div class="ak-event-city-etc">{{ event.attendee_general_location }}</div>
 
     <table class="ak-event-table">
     {% if event.get_starts_at_display %}
@@ -21,6 +23,7 @@
             <th>When:</th>
             <td>
                 {{ event.get_starts_at_display }}
+                {% if event.show_timezone %}{{ event.local_timezone }} time{% endif %}
                 {% if event.is_in_past %}
                     <span class="ak-error ak-event-over-notice">
                         (event is now over)
@@ -31,8 +34,14 @@
     {% endif %}
     {% if event.directions %}
         <tr class="ak-event-directions">
-            <th>Directions to event:</th>
-            <td>{{ event.directions|linebreaksbr }}</td>
+            <th>
+                {% if event.is_virtual %}
+                    To connect:
+                {% else %}
+                    Directions to event:
+                {% endif %}
+            </th>
+            <td style="word-break: break-word; hyphens: auto">{{ event.directions|urlize }}</td>
         </tr>
     {% endif %}
     {% if event.note_to_attendees %}
diff --git a/db_templates/Original/event_attendee_taf_msg.txt b/db_templates/Original/event_attendee_taf_msg.txt
index fb35567848..40200af944 100644
--- a/db_templates/Original/event_attendee_taf_msg.txt
+++ b/db_templates/Original/event_attendee_taf_msg.txt
@@ -5,10 +5,10 @@ Hi,
 
 {% filter single_line %}
 I'm attending an event as part of {% client_name %}'s 
-"{{ event.campaign.local_title }}" campaign.  It's at 
-{{ event.address1 }} {% if event.venue %}({{ event.venue }}){% endif %}
-in {{ event.city }}, {{ event.region }} 
-on {{ event.starts_at|date:"l, F j" }} at {{ event.starts_at|date:"f A" }}.
+"{{ event.campaign.local_title }}" campaign.  It's 
+{% if event.mode == "onsite" %}at {{ event.address1 }} {% if event.venue %}({{ event.venue }}){% endif %}{% else %}{% if event.venue %}on {{ event.venue }}{% else %}online{% endif %}{% endif %}
+{% if event.mode != "global" %}in {% if event.search_show_map %}{{ event.city }}, {% endif %}{{ event.region }}{% endif %} 
+on {{ event.starts_at|date:"l, F j" }} at {{ event.starts_at|date:"f A" }}{% if event.mode == "global" %} {{ event.local_timezone }}{% endif %}.
 {% endfilter %}
 
 RSVP here to join me:
diff --git a/db_templates/Original/event_attendee_tools.html b/db_templates/Original/event_attendee_tools.html
index f96688beaa..99ff16e52f 100644
--- a/db_templates/Original/event_attendee_tools.html
+++ b/db_templates/Original/event_attendee_tools.html
@@ -34,6 +34,7 @@
             {% include "./event_attendee_details.html" %}
         </div>
         
+        {% if event.attendee_show_map %}
         <div class="event-attendee-sidebar">
             <div id="map">
                 <iframe src="https://www.google.com/maps/embed/v1/place?q={{ event.address1|urlencode }},+{{ event.city_etc|urlencode }}&amp;key=AIzaSyCqC-M0AKpFkQtigs-Eda4M4yvC0Xu8fKY" frameborder="0" style="border:0" width="100%" height="300"></iframe>
@@ -44,6 +45,7 @@
                 </form>
             </div>
         </div>
+        {% endif %}
 
     </div><!--col-6-->
 
diff --git a/db_templates/Original/event_create.html b/db_templates/Original/event_create.html
index 87581548fc..5127143f8b 100644
--- a/db_templates/Original/event_create.html
+++ b/db_templates/Original/event_create.html
@@ -113,6 +113,14 @@
 
             <div id="event-info">
                 <h3>Event location</h3>
+                <div {% if campaign.allowed_modes|length < 2 %}style="display: none"{% endif %}>
+					<label for="id_event_mode">Event Type<span class="ak-required-flag">*</span></label>
+					<select id="id_event_mode" name="event_mode">
+                        {% for choice in campaign.allowed_modes %}
+							<option value="{{ choice.0 }}">{{ choice.1 }}</option>
+                        {% endfor %}
+					</select>
+				</div>				
                 <div class="unknown_user">
                     <!--
                         This only shows if the user form includes all of the
@@ -126,15 +134,25 @@
                 <div>
                     {% with "event_" as input_name_prefix %}
                         <div>
-                            <label for="id_event_venue">Venue<span class="ak-required-flag">*</span></label>
+                            <label for="id_event_venue">
+                                <span class="for-mode-onsite">Venue<span class="ak-required-flag">*</span></span>
+                                <span class="for-mode-local for-mode-regional for-mode-global">Platform<span class="ak-required-flag">*</span></span>
+                            </label>
                             <input id="id_event_venue" type="text" if-at-my-house="Home" name="event_venue">
+                            <div class="ak-skip-label-before">
+                            <div class="for-mode-local for-mode-regional for-mode-global">Describe the service or technology being used for this event.</div>
+                            </div>
                         </div>
                         <div>
                             <label for="event_country">Country</label>
                             {% include "./country_select.html" %}
+                            <div class="ak-skip-label-before">
+                            <div class="for-mode-local">Select a location in the local area your event is for.</div>
+                            <div class="for-mode-global">The city and state or region you select below will only be used to determine your local timezone.</div>
+                            </div>
                         </div>
-                        <div>
-                            <label for="id_event_address1">Street address<span class="ak-required-flag">*</span></label>
+                        <div class="for-mode-onsite">
+                            <label for="id_event_address1">Street address<span class="ak-required-flag for-mode-onsite">*</span></label>
                             <input id="id_event_address1" type="text" name="event_address1">
                         </div>
                         <div>
@@ -155,7 +174,7 @@
                         </div>
                         <!-- <div><label for="id_event_phone">Contact phone:</label> <input id="id_event_phone" type="tel" name="event_phone" size="12"></div> -->
                         {% if campaign.show_address1 %}
-                            <div class="strong">
+                            <div class="strong for-mode-onsite">
                                 NOTE: Event addresses are public.
                             </div>
                         {% endif %}
@@ -185,9 +203,15 @@
                 </div>
 
                 <div>
-                    <label for="id_event_directions">Directions to event</label>
+                    <label for="id_event_directions">
+                        <span class="for-mode-onsite">Directions to event</span>
+                        <span class="for-mode-local for-mode-regional for-mode-global">Connection details</span>
+                    </label>
 
                     <textarea id="id_event_directions" name="event_directions"></textarea>
+                        <div class="ak-skip-label-before for-mode-local for-mode-global">
+                            For virtual events, provide information about how people can connect to participate, such as a web page URL, dial-in phone number, or other instructions.
+                        </div>
                 </div>
 
                 <div>
@@ -322,6 +346,12 @@
             }
         }
     };
+    
+    reflectEventMode = function () {
+        var mode = actionkit.utils.val( actionkit.form.event_mode ) || 'onsite';
+        $('.for-mode-onsite, .for-mode-local, .for-mode-regional, .for-mode-global').hide();
+        $('.for-mode-' + mode).show();
+    }
 
     actionkitFormReady =  function() {
         if ( (actionkit.form.event_zip && actionkit.form.event_postal)
@@ -360,6 +390,8 @@
                 $(this).next('label.ak-checkbox-label').removeClass('ak-error');
             }
         });
+        $('#id_event_mode').on('change', reflectEventMode);
+        reflectEventMode();
     });
 
 </script>
@@ -417,3 +450,4 @@ $( function () {
 </script>
 
 {% endblock %}
+
diff --git a/db_templates/Original/event_host_details.html b/db_templates/Original/event_host_details.html
index 3c054a3847..9bc226d7e2 100644
--- a/db_templates/Original/event_host_details.html
+++ b/db_templates/Original/event_host_details.html
@@ -9,8 +9,13 @@
 
 <div class="event-address clearfix">
     <div class="event-address-content">
-        <div class="event-address1">{{ event.address1 }}</div>
-        <div class="event-city-etc">{{ event.city_etc }}</div>
+       {% if event.mode != "onsite" %}
+           <div class="ak-event-mode">{{ event.get_mode_display }}</div>
+       {% endif %}
+        {% if event.attendee_show_address1 %}
+        <div class="ak-event-address1">{{ event.address1 }}</div>
+        {% endif %}
+        <div class="ak-event-city-etc">{{ event.attendee_general_location }}</div>
     </div>
 </div>
 
@@ -18,13 +23,23 @@
 {% if event.get_starts_at_display %}
     <tr class="ak-event-time">
         <th>When:</th>
-        <td>{{ event.get_starts_at_display }}{% if event.is_in_past %} <span class="ak-error ak-event-over-notice">(event is now over)</span>{% endif %}</td>
+        <td>
+            {{ event.get_starts_at_display }}
+            {% if event.show_timezone %}{{ event.local_timezone }} time{% endif %}
+            {% if event.is_in_past %}<span class="ak-error ak-event-over-notice">(event is now over)</span>{% endif %}
+        </td>
     </tr>
 {% endif %}
 {% if event.directions %}
     <tr class="ak-event-directions">
-        <th>Directions to event:</th>
-        <td>{{ event.directions|linebreaksbr }}</td>
+        <th>
+            {% if event.is_virtual %}
+                To connect:
+            {% else %}
+                Directions to event:
+            {% endif %}
+        </th>
+        <td style="word-break: break-word; hyphens: auto">{{ event.directions|urlize }}</td>
     </tr>
 {% endif %}
 {% if event.note_to_attendees %}
diff --git a/db_templates/Original/event_search.html b/db_templates/Original/event_search.html
index a5f3819f91..8db7e234a4 100644
--- a/db_templates/Original/event_search.html
+++ b/db_templates/Original/event_search.html
@@ -51,6 +51,7 @@
                 <ul class="compact" id="ak-errors"></ul>
 
                 <div class="ak-event-search">
+                    {% if campaign.allows_geographic_modes %}
                     <div class="ak-display-inline-block">
                         <label for="id_zip">{% if form.templateset.is_intl %}Postcode{% else %}ZIP{% endif %} or City:</label>
                         <input type="text" name="place" value="{% if args.place %}{{args.place}}{% else %}{{args.zip}}{% endif %}">
@@ -62,6 +63,7 @@
                         </div>
                     {% endif %}
                     <button type="submit" class="ak-event-search">Search</button>
+                    {% endif %}
                     {% if campaign.also_search_campaigns.all %}
                     <div>
                         <input type="hidden" name="also_search_present" value="1">
diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index c465816216..126c1338f9 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -23,7 +23,13 @@
     	{% if not hide_map %}
     		<p>There are <strong>{{ open_events|length }}</strong> events open for signup {% if is_in_past or is_full %}({% if is_full %}plus <strong>{{ is_full|length }}</strong> full{% endif %}{% if is_in_past and is_full %}, {% endif %}{% if is_in_past and not is_full %}plus {% endif %}{% if is_in_past %}<strong>{{ is_in_past|length }}</strong> ended{% endif %}){% endif %}</p>
     	{% endif %}
-        {% if campaign.show_address1 and not hide_map and events|length < 300 %}
+        {% with "[]"|load_json as empty_array %}{% remember empty_array as mappable_events %}{% endwith %}
+        {% for event in events %}
+            {% if "Global" not in event.mode %}
+                {% record event in mappable_events %}
+            {% endif %}
+        {% endfor %}
+        {% if campaign.show_address1 and not hide_map and mappable_events|length and mappable_events|length < 300 %}
 			<style>
 				#map {
 					   width: 100%;
@@ -35,7 +41,7 @@
 			<script>
 				/*List to hold events*/
 				var loc = [];
-				{% for event in events %}
+				{% for event in mappable_events %}
 					loc.push([ 
 							{% if event.is_in_past or event.is_full %}
 								'<p>{{ event.starts_at_dt|date:"l, F jS, G:i a" }}</p>' +
@@ -44,7 +50,7 @@
 								' class="ak-campaign-title">' + 
 								{% endif %}
 								'{{ event.public_title }}</a></p>' +
-								'<p>{{ event.address1 }} <br />{{ event.city }} {{ event.state }} {{ event.zip }} </p>' +
+								'<p>{% if event.search_show_address1 %}{{ event.address1 }}<br>{% endif %}{{ event.search_general_location }}</p>' +
 								'{% if event.is_in_past %} <p><strong>Sorry, the event is over.</strong></p> {% endif %} {% if event.is_full %} <p><strong>Sorry, the event is full.</strong></p> {% endif %}'
 							{% else %}
 								'<p>{{ event.starts_at_dt|date:"l, F jS, G:i a" }}</p>' +
@@ -53,7 +59,7 @@
 								' class="ak-campaign-title">' + 
 								{% endif %}
 								'{{ event.public_title }}</a></p>' +
-								'<p>{{ event.address1 }} <br />{{ event.city }} {{ event.state }} {{ event.zip }} </p>' +
+								'<p>{% if event.search_show_address1 %}{{ event.address1 }}<br>{% endif %}{{ event.search_general_location }}</p>' +
 								'<a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}" class="ak-button">Sign up</a>'
 							{% endif %},
 							{{ event.latitude }},
@@ -76,7 +82,7 @@
 				{% endif %}
 
 				/*Initialize the map*/
-				var first_event_coordinates = [{{ events.0.latitude }}, {{ events.0.longitude }}]
+				var first_event_coordinates = [{{ mappable_events.0.latitude }}, {{ mappable_events.0.longitude }}]
 				var map = L.map('map').setView(first_event_coordinates, {% if all %}2{% else %}12{% endif %});
 				L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
 				   maxZoom: 19,
@@ -109,10 +115,7 @@
     	{% endif %}
 		
         {% for event in events %}   	
-        	{% if event.is_in_past or event.is_full %}
-        	
-        	{% else %}
-        	{% if not all %}
+        	{% if "Global" in event.mode or not all %}
 				<div class="ak-field-box {% if event.is_in_past or event.is_full %}ak-event-disabled{% endif %}">
 					<div class="ak-info-column">
 						<p class="ak-event-title">
@@ -125,19 +128,15 @@
 						<span class="ak-event-venue">{{ event.venue }}</span>
 					{% endif %}
 			
-					{% if event.campaign.show_address1 %}
+					{% if event.search_show_address1 %}
 						<div class="ak-event-address1">{{ event.address1 }}</div>
 					{% endif %}
 
-					{% if event.campaign.show_city or event.campaign.show_state or event.campaign.show_zip %}
-						{% if event.campaign.show_zip %}
-							<div class="ak-event-city-etc">{{ event.city_etc }}</div>
-						{% else %}
-							<div class="ak-event-city-etc">{{ event.city_etc_no_postal }}</div>
-						{% endif %}
+					{% if event.search_show_general_location %}
+						<div class="ak-event-city-etc">{{ event.search_general_location }}</div>
 					{% endif %}
 
-					{% if event.distance|is_nonblank %}
+					{% if event.has_distance %}
 						<p><span class="ak-event-distance">{{ event.distance_str }} away</a></span></p>
 					   {% endif %}
 		
@@ -145,12 +144,18 @@
 						{% if event.starts_at %}
 							<tr class="ak-event-time">
 								<th>When:</th>
-								<td>{{ event.starts_at_dt|date:"l, F jS, G:i a" }}</td>
+								<td>{{ event.starts_at_dt|date:"l, F jS, G:i a" }} {% if "Global" in event.mode %}{{ event.local_timezone }} time{% endif %}</td>
 							</tr>
 						{% endif %}
 						{% if event.campaign.show_directions and event.directions %}
 							<tr class="ak-event-directions">
-								<th>Directions:</th> 
-								<td>{{ event.directions|linebreaksbr }}</span></td>
+								<th>
+									{% if event.is_virtual %}
+										To connect:
+									{% else %}
+										Directions:
+									{% endif %}
+								</th>
+                                <td style="word-break: break-word; hyphens: auto">{{ event.directions|urlize }}</td>
 							</tr>
 						{% endif %}
 						{% if event.campaign.show_attendee_count %}
@@ -162,7 +167,7 @@
 						{% endif %}
 					</table>
 					</div>
-					<div class="ak-description-column">
+					<div class="ak-description-column" style="width: 100%">
 					{% if event.campaign.show_public_description %}
 						{% if event.public_description %}
 							<p class="ak-event-description">{{ event.public_description|linebreaksbr }}</p>
@@ -170,9 +175,7 @@
 					{% endif %}
 					</div>
 				</div>
-			{% endif %}
             {% endif %}
-            
         {% endfor %}
     {% endif %}
     {% if campaign.public_create_page %}

2.4.9 (2020 June 2)

No changes.

2.4.8 (2020 May 20)

No changes.

2.4.7 (2020 May 6)

No changes.

2.4.6 (2020 April 20)

No changes.

2.4.5 (2020 March 31)

Support unchecking "Require Hosts to Confirm Email"

diff --git a/db_templates/Original/event_created.html b/db_templates/Original/event_created.html
index bd98e3950b..afa51799d1 100644
--- a/db_templates/Original/event_created.html
+++ b/db_templates/Original/event_created.html
@@ -4,8 +4,23 @@
 {% block content %}
 <div class="ak-grid-row">
     <div class="ak-grid-col ak-grid-col-12-of-12">
-        <h2>Confirm Your Event</h2>
-        <p>Check your email {% if args.email %}at {{args.email}}{% endif %} to finish creating your event.</p>
+        {% with action.event.campaign as campaign %}
+        {% if campaign.require_email_confirmation and campaign.create_page.followup.send_email %}
+            <h2>Confirm Your Event</h2>
+            <p>Check your email {% if args.email %}at {{args.email}}{% endif %} to finish creating your event.</p>
+        {% elif campaign.require_staff_approval %}
+            <h2>Staff approval required</h2>
+            <p>Your event must be approved by staff before it will be open for signups; when it is approved, you'll receive an email.</p>
+        {% else %}
+            {% if campaign.create_page.followup.send_email %}
+                <h2>View Host Tools</h2>
+                <p>Thanks for signing up to host an event! Now check your email {% if args.email %}at {{args.email}}{% endif %} to view your host tools.</p>
+            {% else %}
+                <h2>Thank you!</h2>
+                <p>Thanks for signing up to host an event! You'll hear from us soon.</p>
+            {% endif %}
+        {% endif %}
+        {% endwith %}
     </div>
 </div>
 {% endblock %}
diff --git a/db_templates/Original/event_create.html b/db_templates/Original/event_create.html
index 5e520b07c6..87581548fc 100644
--- a/db_templates/Original/event_create.html
+++ b/db_templates/Original/event_create.html
@@ -259,7 +259,7 @@
 
         {% if not update %}
         <p>
-            Next, we'll email you a link to confirm your event.
+            Next, we'll email you a link to {% if campaign.require_email_confirmation %}confirm{% else%}manage{% endif %} your event.
         </p>
         {% else %}
         <div>
@@ -270,7 +270,7 @@
         </div>
         {% endif %}
 
-        <input id="id_submit" type="submit" value="{% if update %}Update event{% else %}Continue to next step: Confirm event{% endif %}" class="ak-submit-button">
+        <input id="id_submit" type="submit" value="{% if update %}Update event{% elif campaign.require_email_confirmation %}Continue to next step: Confirm event{% else %}Save event{% endif %}" class="ak-submit-button">
 
     </div>
 </div>
diff --git a/db_templates/Original/event_email_created.html b/db_templates/Original/event_email_created.html
index 107124aaf1..c2ad4c2085 100644
--- a/db_templates/Original/event_email_created.html
+++ b/db_templates/Original/event_email_created.html
@@ -1,10 +1,13 @@
-Subject: Confirm your "{{ action.event.campaign.local_title }}" event
+Subject: {% if action.event.campaign.require_email_confirmation %}Confirm{% else %}Manage{% endif %} your "{{ action.event.campaign.local_title }}" event
 
 {% load actionkit_tags %}
 <!-- event_email_created.html in the templateset contains the default
      confirmation email for hosts -->
 
-<p>Thanks for setting up a "{{ action.event.campaign.local_title }}" event.  <b>Before anyone can sign up for your event, you'll need to click the link below to access your host tools and confirm your event.</b></p>
+<p>Thanks for setting up a "{{ action.event.campaign.local_title }}" event.  {% if action.event.campaign.require_email_confirmation %}<b>Before anyone can sign up for your event, you'll need to click the link below to access your host tools and confirm your event.</b>
+{% else %}
+Please click the link below to access your host tools:
+{% endif %}
 
 <p><a href="https://{% client_ssl_domain %}/event/{{ action.event.campaign.local_name }}/{{ action.event.id }}/host/?i={{ user|login_string }}&l=1">https://{% client_ssl_domain %}/event/{{ action.event.campaign.local_name }}/{{ action.event.id }}/host/?i={{ user|login_string }}&l=1</a></p>
 

Add to /me a cross-campaign event list and number of upcoming events in moderation

diff --git a/db_templates/Original/user_view.html b/db_templates/Original/user_view.html
index 6d4f165ade..ec5311186c 100644
--- a/db_templates/Original/user_view.html
+++ b/db_templates/Original/user_view.html
@@ -64,13 +64,47 @@
         </ul>
         {% endif %}
 
-        {% for campaign in campaigns_moderating %}
+        {% for campaign, upcoming_event_count in campaigns_moderating.items %}
           {% if forloop.first %}
             <h3>Event Moderation</h3>
             <ul>
           {% endif %}
           <li>
-            <b>{{ campaign.title }}</b> campaign with {{ campaign.event_set.all|length }} events. <a href='/event/{{campaign.moderate_page.name}}/moderator_search/?akid={{akid}}'>Moderate</a>
+            <b>{{ campaign.title }}</b> campaign with <b>{{ upcoming_event_count }}</b> upcoming events ({{ campaign.event_set.count }} total). <a href='/event/{{campaign.moderate_page.name}}/moderator_search/?akid={{akid}}'>Moderate</a>
+          </li>
+          {% if forloop.last %}
+            </ul>
+          {% endif %}
+        {% endfor %}
+
+        {% for event in upcoming_events %}
+          {% if forloop.first %}
+            <h3>Upcoming Events</h3>
+            <ul>
+          {% endif %}
+          <li>
+            <b>{{ event.event.starts_at_dt|date:"l, F j, Y, g:i A" }}:</b>
+            {% if event.role == 'host' %}Hosting <a href="{% url 'event_host_tools' event.event.campaign.local_name event.event.id %}?action_id={{ event.action.id }}&akid={{ event.action.user.akid }}">
+            {% else %} Attending <a href="{% url 'event_attendee_tools' event.event.campaign.local_name event.event.id %}?action_id={{ event.action.id }}&akid={{ event.action.user.akid }}">
+            {% endif %}
+            {{ event.event.title }}</a>
+          </li>
+          {% if forloop.last %}
+            </ul>
+          {% endif %}
+        {% endfor %}
+
+        {% for event in past_events %}
+          {% if forloop.first %}
+            <h3>Past Events</h3>
+            <ul>
+          {% endif %}
+          <li>
+            <b>{{ event.event.starts_at_dt|date:"l, F j, Y, g:i A" }}:</b>
+            {% if event.role == 'host' %}Hosted <a href="{% url 'event_host_tools' event.event.campaign.local_name event.event.id %}?action_id={{ event.action.id }}&akid={{ event.action.user.akid }}">
+            {% else %} Attended <a href="{% url 'event_attendee_tools' event.event.campaign.local_name event.event.id %}?action_id={{ event.action.id }}&akid={{ event.action.user.akid }}">
+            {% endif %}
+            {{ event.event.title }}</a>
           </li>
           {% if forloop.last %}
             </ul>

Fix problem where line breaks weren't displayed on most event pages

diff --git a/db_templates/Original/wrapper.html b/db_templates/Original/wrapper.html
index 8272445f12..b6bf825296 100644
--- a/db_templates/Original/wrapper.html
+++ b/db_templates/Original/wrapper.html
@@ -254,6 +254,7 @@
     {% endif %}
     .ak-target-phone .ak-target-label, span.office_phone_label,
         span.ak-target-fax, span.ak-target-separator { display: none }
+    .ak-event-description, .ak-event-note-to-attendees, .ak-event-directions { white-space: pre-line; }
     </style>
     {% block script_additions %}{% endblock %}
 </head>
diff --git a/db_templates/Original/event_attendee_details.html b/db_templates/Original/event_attendee_details.html
index 9091d72d23..0db6d3eb1c 100644
--- a/db_templates/Original/event_attendee_details.html
+++ b/db_templates/Original/event_attendee_details.html
@@ -32,21 +32,19 @@
     {% if event.directions %}
         <tr class="ak-event-directions">
             <th>Directions to event:</th>
-            <td>{{ event.directions }}</td>
+            <td>{{ event.directions|linebreaksbr }}</td>
         </tr>
     {% endif %}
     {% if event.note_to_attendees %}
         <tr class="ak-event-note-to-attendees">
             <th>Your host says:</th>
-            <td>{{ event.note_to_attendees }}</td>
+            <td>{{ event.note_to_attendees|linebreaksbr }}</td>
         </tr>
     {% endif %}
     </table>
 
     {% if event.public_description %}
-        <p class="ak-event-description">
-            {{ event.public_description }}
-        </p>
+        <p class="ak-event-description">{{ event.public_description|linebreaksbr }}</p>
     {% endif %}
 
 </div>
diff --git a/db_templates/Original/event_host_details.html b/db_templates/Original/event_host_details.html
index 31c9feb772..3c054a3847 100644
--- a/db_templates/Original/event_host_details.html
+++ b/db_templates/Original/event_host_details.html
@@ -24,13 +24,13 @@
 {% if event.directions %}
     <tr class="ak-event-directions">
         <th>Directions to event:</th>
-        <td>{{ event.directions }}</td>
+        <td>{{ event.directions|linebreaksbr }}</td>
     </tr>
 {% endif %}
 {% if event.note_to_attendees %}
     <tr class="ak-event-note-to-attendees">
         <th>Note to attendees:</th>
-        <td>{{ event.note_to_attendees }}</td>
+        <td>{{ event.note_to_attendees|linebreaksbr }}</td>
     </tr>
 {% endif %}
 {% if event.campaign.allow_private %}
@@ -48,9 +48,7 @@
 </table>
 
 {% if event.public_description %}
-    <p class="ak-event-description">
-        {{ event.public_description }}
-    </p>
+    <p class="ak-event-description">{{ event.public_description|linebreaksbr }}</p>
 {% endif %}
 
 {% endfilter %}
diff --git a/db_templates/Original/event_moderate_details.html b/db_templates/Original/event_moderate_details.html
index f39adb9d95..ae2246386f 100644
--- a/db_templates/Original/event_moderate_details.html
+++ b/db_templates/Original/event_moderate_details.html
@@ -24,13 +24,13 @@
     {% if event.directions %}
     <tr class="ak-event-directions">
         <th>Directions to event:</th>
-        <td>{{ event.directions }}</td>
+        <td>{{ event.directions|linebreaksbr }}</td>
     </tr>
     {% endif %}
     {% if event.note_to_attendees %}
     <tr class="ak-event-note-to-attendees">
         <th>Note to attendees:</th>
-        <td>{{ event.note_to_attendees }}</td>
+        <td>{{ event.note_to_attendees|linebreaksbr }}</td>
     </tr>
     {% endif %}
     {% if event.campaign.allow_private %}
@@ -63,9 +63,7 @@
     </tr>
 </table>
 {% if event.public_description %}
-    <p class="ak-event-description">
-        {{ event.public_description }}
-    </p>
+    <p class="ak-event-description">{{ event.public_description|linebreaksbr }}</p>
 {% endif %}
 
 {% endfilter %}
diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index 8bda4992c7..c465816216 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -151,7 +151,7 @@
                        {% if event.campaign.show_directions and event.directions %}
                            <tr class="ak-event-directions">
                                <th>Directions:</th> 
-                               <td>{{ event.directions }}</span></td>
+                               <td>{{ event.directions|linebreaksbr }}</span></td>
                            </tr>
                        {% endif %}
                        {% if event.campaign.show_attendee_count %}
@@ -165,9 +165,7 @@
                    <div class="ak-description-column">
                    {% if event.campaign.show_public_description %}
                        {% if event.public_description %}
-                           <p class="ak-event-description">
-                               {{ event.public_description }}
-                           </p>
+                           <p class="ak-event-description">{{ event.public_description|linebreaksbr }}</p>
                        {% endif %}
                    {% endif %}
                    </div>

Adds an embedded Quickdonate form to donate pages that are using a FastAction account

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index bc05adbc70..ceb2e14f58 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -1,6 +1,6 @@
 {% extends "./wrapper.html" %}
 {% load actionkit_tags %}
-
+{% block body_extra_classes %}{% if page.payment_processor_information.is_quickdonate %}actionkit-donate-qd {% endif %}{{ block.super }}{% endblock %}
 {% block content %}
 <form id="act" name="act" class="{{ templateset.custom_fields.donation_steps }}" method="POST" action="/act/" accept-charset="utf-8">
     <input type="hidden" name="page" value="{{ page.name }}">
@@ -1448,6 +1448,14 @@ $(function() {
 });
 </script>
 {% endif %}
+
 {% endwith %}
 
 {% endblock %}
+
+{% block below_form %}
+  {% if page.payment_processor_information.is_quickdonate %}
+    {% load_quickdonate %}
+  {% endif %}
+  {{ block.super }}
+{% endblock %}

2.4.4 (2020 March 5)

Allow moderators to edit an event's details even if the EventCreatePage is hidden

diff --git a/db_templates/Original/event_host_tools.html b/db_templates/Original/event_host_tools.html
index 121f539cd7..9a9fc2f3ab 100644
--- a/db_templates/Original/event_host_tools.html
+++ b/db_templates/Original/event_host_tools.html
@@ -183,7 +183,7 @@
                             {% endif %}
                         </li>
                         {% endif %}
-                        <li><a href="{% url 'create_event' event.local_campaign.create_page.name %}?action_id={{ action.id }}&amp;update=1&amp;want_prefill_data=1">Edit event details</a></li>
+                        <li><a href="{% url 'create_event' event.local_campaign.create_page.name %}?action_id={{ action.id }}&amp;update=1&amp;want_prefill_data=1{% if host %}&amp;host_akid={{ host.akid }}{% endif %}">Edit event details</a></li>
                         {% if cohosts %}
                         <li class="if-js"><a class="jump-link" id="email-cohosts-link" href="#contact-cohosts">Email cohost{{ cohosts|length|pluralize }}</a></li>
                         {% endif %}
@@ -216,7 +216,7 @@
                <div id="host-event-details ak-clearfix">
                    <h3>
                        Event Details
-                       {% block event_edit_link %}(<a class="ak-underline-on-hover" href="{% url 'create_event' event.local_campaign.create_page.name %}?action_id={{ action.id }}&amp;update=1&amp;want_prefill_data=1">Edit</a>){% endblock %}
+                       {% block event_edit_link %}(<a class="ak-underline-on-hover" href="{% url 'create_event' event.local_campaign.create_page.name %}?action_id={{ action.id }}&amp;update=1&amp;want_prefill_data=1{% if host %}&amp;host_akid={{ host.akid }}{% endif %}">Edit</a>){% endblock %}
                    </h3>
                    {% include "./event_host_details.html" %}
                </div>
diff --git a/db_templates/Original/event_moderate_tools.html b/db_templates/Original/event_moderate_tools.html
index e625eaeb8d..e4361db73f 100644
--- a/db_templates/Original/event_moderate_tools.html
+++ b/db_templates/Original/event_moderate_tools.html
@@ -67,7 +67,7 @@ $(initModeratorTools);
              {% endif %}
              {% if event.campaign.allow_moderator_edits and hosts %}
                 {% with host=hosts.0 %}
-                   <li><a href="/event/{{event.local_campaign.create_page.name}}/create/?action_id={{ host.action.id }}&amp;update=1&amp;want_prefill_data=1&akid={{ host.user.akid }}">Edit event details</a></li>
+                   <li><a href="/event/{{event.local_campaign.create_page.name}}/create/?action_id={{ host.action.id }}&amp;update=1&amp;want_prefill_data=1&amp;akid={{ host.user.akid }}&amp;mod_akid={{ user.akid }}">Edit event details</a></li>
                 {% endwith %}
              {% endif %}
              <li><a class="jump-link" id="email-hosts-link" href="#contact-hosts">Email host{{ hosts|length|pluralize }}</a></li>
@@ -105,7 +105,7 @@ $(initModeratorTools);
                  {% block event_edit_link %}
                      {% if event.campaign.allow_moderator_edits and hosts %}
                          {% with host=hosts.0 %}
-                             (<a class="ak-underline-on-hover" href="/event/{{event.local_campaign.create_page.name}}/create/?action_id={{ host.action.id }}&amp;update=1&amp;want_prefill_data=1&akid={{ host.user.akid }}">Edit</a>)
+                             (<a class="ak-underline-on-hover" href="/event/{{event.local_campaign.create_page.name}}/create/?action_id={{ host.action.id }}&amp;update=1&amp;want_prefill_data=1&amp;akid={{ host.user.akid }}&amp;mod_akid={{ user.akid }}">Edit</a>)
                          {% endwith %}
                      {% endif %}
                  {% endblock %}

2.4.3 (2020 February 12)

2.4.2 (2020 January 28)

diff --git a/db_templates/Original/event_search_moderator.html b/db_templates/Original/event_search_moderator.html
index 4c77d47ff3..4e978df86d 100644
--- a/db_templates/Original/event_search_moderator.html
+++ b/db_templates/Original/event_search_moderator.html
@@ -80,6 +80,11 @@
                    <input type="text" name="states" id="id_states">
                    <span class="ak-instructions">E.g., AK, CA</span>
                </div>
+    <div class="ak-grid-row ak-margin-bottom-5">
+      <label for="id_districts">Limit to US district(s):</label>
+      <input type="text" name="districts" id="id_districts">
+      <span class="ak-instructions">E.g., AK_01, CA_51</span>
+    </div>
                <div class="ak-grid-row ak-margin-bottom-5">
                    <div class="ak-display-inline-block">
                        <label for="id_country">Limit by country:</label>

2.4.1 (2020 January 8)

Adds a new feature to allow sending an email to signed up attendees when event details change

diff --git a/db_templates/Original/event_email_details_changed.html b/db_templates/Original/event_email_details_changed.html
index 0000000000..40f08a8485 100644
--- a/db_templates/Original/event_email_details_changed.html
+++ db_templates/Original/event_email_details_changed.html
@@ -0,0 +1,16 @@
+Subject: Event details changed for your "{{ event.title }}" event
+
+{% load actionkit_tags %}
+
+This is a notification that an event you're signed up for has been changed:
+{% for field, change in changes.items %}
+<li><b>{{ field }}</b> changed to {{ change.1 }}</li>
+{% endfor %}
+
+<p>To view the full event details, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}">https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}</a></p>
+
+<!-- Replacement for unsubscribe. -->
+<p><small>
+    You're receiving this message because you're signed up for an event.  
+    You can use your event tools, linked above, to cancel your signup and quit receiving these messages.
+</small></p>
diff --git a/db_templates/Original/event_create.html b/db_templates/Original/event_create.html
index fa53a348f4..5e520b07c6 100644
--- a/db_templates/Original/event_create.html
+++ b/db_templates/Original/event_create.html
@@ -261,6 +261,13 @@
         <p>
             Next, we'll email you a link to confirm your event.
         </p>
+        {% else %}
+        <div>
+            <input id="id_send_details_changed_email" type="checkbox" name="send_details_changed_email" value="1">
+            <label class="ak-checkbox-label" for="id_send_details_changed_email">
+                Send an email to attendees notifying them of the event details changing
+            </label>
+        </div>
         {% endif %}
 
         <input id="id_submit" type="submit" value="{% if update %}Update event{% else %}Continue to next step: Confirm event{% endif %}" class="ak-submit-button">

2.3.59 (2019 November 6)

2.3.58 (2019 October 10)

Default to using a login string rather than making event hosts set a password

diff --git a/db_templates/Original/event_email_approved.html b/db_templates/Original/event_email_approved.html
index 90d94e323b..28418967d3 100644
--- a/db_templates/Original/event_email_approved.html
+++ b/db_templates/Original/event_email_approved.html
@@ -9,7 +9,9 @@ Subject: Your "{{campaign_title}}" event has been approved!
 <p>You can start sharing your event at <a href="{% client_domain_url event.public_url %}">{% client_domain_url event.public_url %}</a> with potential attendees. </p>
 {% endfilter %}
 
-<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}">https://{% client_ssl_domain %}{{ signup.url }}</a></p>
+<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}&i={{ user|login_string }}&l=1">https://{% client_ssl_domain %}{{ signup.url }}&i={{ user|login_string }}&l=1</a></p>
+
+<p>Please <b>don't forward this email</b>: the link above is personalized, and can be used to manage your event or change your account password.</p>
 
 <p>Thanks again!</p>
 
diff --git a/db_templates/Original/event_email_created.html b/db_templates/Original/event_email_created.html
index 943f876af4..107124aaf1 100644
--- a/db_templates/Original/event_email_created.html
+++ b/db_templates/Original/event_email_created.html
@@ -4,9 +4,9 @@ Subject: Confirm your "{{ action.event.campaign.local_title }}" event
 <!-- event_email_created.html in the templateset contains the default
      confirmation email for hosts -->
 
-<p>Thanks for setting up a "{{ action.event.campaign.local_title }}" event.  <b>Before anyone can sign up for your event, you must confirm it by clicking below to {% if not user.password %}set a password and{% endif %} log in to your host tools:</b></p>
+<p>Thanks for setting up a "{{ action.event.campaign.local_title }}" event.  <b>Before anyone can sign up for your event, you'll need to click the link below to access your host tools and confirm your event.</b></p>
 
-<p><a href="https://{% client_ssl_domain %}/event/{{ action.event.campaign.local_name }}/{{ action.event.id }}/host/?i={{ user.password_change_token }}">https://{% client_ssl_domain %}/event/{{ action.event.campaign.local_name }}/{{ action.event.id }}/host/?i={{ user.password_change_token }}</a></p>
+<p><a href="https://{% client_ssl_domain %}/event/{{ action.event.campaign.local_name }}/{{ action.event.id }}/host/?i={{ user|login_string }}&l=1">https://{% client_ssl_domain %}/event/{{ action.event.campaign.local_name }}/{{ action.event.id }}/host/?i={{ user|login_string }}&l=1</a></p>
 
 <p>Please <b>don't forward this email</b>: the link above is personalized, and can be used to manage your event or change your account password.</p>
 
diff --git a/db_templates/Original/event_email_from_admin.html b/db_templates/Original/event_email_from_admin.html
index 2fd5e36e5b..cfbae87e3c 100644
--- a/db_templates/Original/event_email_from_admin.html
+++ b/db_templates/Original/event_email_from_admin.html
@@ -5,7 +5,11 @@ Subject: {{subject}}
 
 <hr>
 
-<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}">https://{% client_ssl_domain %}{{ signup.url }}</a></p>
+<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}">https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}</a></p>
+
+{% if signup.role == "host" %}
+<p>Please <b>don't forward this email</b>: the link above is personalized, and can be used to manage your event or change your account password.</p>
+{% endif %}
 
 <!-- Replacement for unsubscribe. -->
 <p><small>
diff --git a/db_templates/Original/event_email_from_attendee.html b/db_templates/Original/event_email_from_attendee.html
index 4e5b5afaf7..63feecfbd1 100644
--- a/db_templates/Original/event_email_from_attendee.html
+++ b/db_templates/Original/event_email_from_attendee.html
@@ -8,7 +8,11 @@ Subject: Your "{{campaign_title}}" attendee sent you a message
 {{ message|linebreaksbr }}
 </blockquote>
 
-<p>To use your event tools, visit https://{% client_ssl_domain %}{{ signup.url }}</p>
+<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}">https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}</a></p>
+
+{% if signup.role == "host" %}
+<p>Please <b>don't forward this email</b>: the link above is personalized, and can be used to manage your event or change your account password.</p>
+{% endif %}
 
 <!-- Replacement for unsubscribe. -->
 <p><small>
diff --git a/db_templates/Original/event_email_from_host.html b/db_templates/Original/event_email_from_host.html
index 448bd6965b..4bc2df4ffc 100644
--- a/db_templates/Original/event_email_from_host.html
+++ b/db_templates/Original/event_email_from_host.html
@@ -8,7 +8,11 @@ Subject: {{subject}}
 {{ message|linebreaksbr }}
 </blockquote>
 
-<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}">https://{% client_ssl_domain %}{{ signup.url }}</a></p>
+<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}">https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}</a></p>
+
+{% if signup.role == "host" %}
+<p>Please <b>don't forward this email</b>: the link above is personalized, and can be used to manage your event or change your account password.</p>
+{% endif %}
 
 <!-- Replacement for unsubscribe. -->
 <p><small>
diff --git a/db_templates/Original/event_email_from_moderator.html b/db_templates/Original/event_email_from_moderator.html
index 2924837052..ea2fedc7f7 100644
--- a/db_templates/Original/event_email_from_moderator.html
+++ b/db_templates/Original/event_email_from_moderator.html
@@ -7,7 +7,11 @@ Subject: {{subject}}
 
 <hr>
 
-<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}">https://{% client_ssl_domain %}{{ signup.url }}</a></p>
+<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}">https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}</a></p>
+
+{% if signup.role == "host" %}
+<p>Please <b>don't forward this email</b>: the link above is personalized, and can be used to manage your event or change your account password.</p>
+{% endif %}
 
 <!-- Replacement for unsubscribe. -->
 <p><small>
diff --git a/db_templates/Original/event_email_role_changed.html b/db_templates/Original/event_email_role_changed.html
index e2f2b272fe..28abeda205 100644
--- a/db_templates/Original/event_email_role_changed.html
+++ b/db_templates/Original/event_email_role_changed.html
@@ -3,7 +3,11 @@ Subject: You are now a {{new_role}} of your "{{campaign_title}}" event
 {% load actionkit_tags %}
 <p>You are now a {{ signup.role }} of your "{{ campaign.local_title }}" event.</p>
 
-<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}">https://{% client_ssl_domain %}{{ signup.url }}</a></p>
+<p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}">https://{% client_ssl_domain %}{{ signup.url }}{% if signup.role == "host" %}&i={{ user|login_string }}&l=1{% endif %}</a></p>
+
+{% if signup.role == "host" %}
+<p>Please <b>don't forward this email</b>: the link above is personalized, and can be used to manage your event or change your account password.</p>
+{% endif %}
 
 <!-- Replacement for unsubscribe. -->
 <p><small>
diff --git a/db_templates/Original/event_email_volunteer_approved.html b/db_templates/Original/event_email_volunteer_approved.html
index 691baa8d26..c0971c369f 100644
--- a/db_templates/Original/event_email_volunteer_approved.html
+++ b/db_templates/Original/event_email_volunteer_approved.html
@@ -5,7 +5,7 @@ Subject: You have been approved to moderate "{{campaign_title}}"!
 
 <p>Your offer to moderate "{{campaign_title}}" has been approved and you're ready to get started.</p>
 
-<p>To get started and find events, visit <a href="https://{% client_ssl_domain %}{{signup.url}}?i={{ user.password_change_token }}">https://{% client_ssl_domain %}{{signup.url}}?i={{ user.password_change_token }}</a></p>
+<p>To get started and find events, visit <a href="https://{% client_ssl_domain %}{{signup.url}}?i={{ user|login_string }}&l=1">https://{% client_ssl_domain %}{{signup.url}}?i={{ user|login_string }}&l=1</a></p>
 
 <p>Please <strong>don't forward or share this email</strong>: the link above is personalized, and can be used to view your personal information and moderate {% client_name %}'s events.</p>
 

2.3.57 (2019 September 10)

2.3.56 (2019 July 29)

2.3.55 (2019 July 10)

No changes.

2.3.54 (2019 June 12)

No changes.

2.3.53 (2019 June 4)

Let your users search across multiple event campaigns using the new "Also Search Campaigns" feature

diff --git a/db_templates/Original/event_attend.html b/db_templates/Original/event_attend.html
index aa5a969b92..d3d5b0925f 100644
--- a/db_templates/Original/event_attend.html
+++ b/db_templates/Original/event_attend.html
@@ -85,39 +85,18 @@ $(window).on('load', function (){
                 {% if not update %}
                 <p>
                     There
-                    {% if event.is_open_for_signup and open_events|length|subtract:1 > 0 %}
-                        {% if open_events|length|subtract:2 > 0 %}
-                            are
-                        {% else %}
-                            is
-                        {% endif %}
-                        <strong>
-                            {{ open_events|length|subtract:1 }}
-                            other event{{ open_events|length|subtract:1|pluralize }}
-                        </strong>
-                    {% else %}
-                        {% if open_events|length|subtract:1 > 0 %}
-                            are
-                        {% else %}
-                            is
-                        {% endif %}
-                        <strong>
-                            {{ open_events|length }}
-                            event{{ open_events|length|pluralize }}
-                        </strong>
-                    {% endif %}
-                    open for signup{% if is_in_past or is_full %}({% if is_full %}
+                    {% if other_open_events == 1 %}is{% else %}are{% endif %}
+                    <strong>
+                        {{ other_open_events }}
+                        {% if event.is_open_for_signup %}other{% endif %}
+                        event{{ other_open_events|pluralize }}
+                    </strong>
+                    open for signup{% if is_in_past or is_full %} ({% if is_full %}
                     plus <strong>{{ is_full|length }}</strong> full{% endif %}{% if is_in_past and is_full %}, {% endif %}
                     {% if is_in_past and not is_full %} plus {% endif %}
                     {% if is_in_past %}<strong>{{ is_in_past|length }}</strong> ended{% endif %}){% endif %}.
-                    {% if event.is_open_for_signup %}
-                        {% if open_events|length|subtract:1 > 0 %}
-                            <a href="/event/{{ campaign.local_name }}/?akid={{args.akid}}&amp;zip={{args.zip}}">Search for another event</a>.
-                        {% endif %}
-                    {% else %}
-                        {% if open_events|length > 0 %}
-                            <a href="/event/{{ campaign.local_name }}/?akid={{args.akid}}&amp;zip={{args.zip}}">Search for another event</a>.
-                        {% endif %}
+                    {% if other_open_events > 0 %}
+                        <a href="/event/{{ search_campaign.local_name }}/?akid={{args.akid}}&amp;zip={{args.zip}}">Search for another event</a>.
                     {% endif %}
                 </p>
                 {% endif %}
@@ -212,7 +191,7 @@ $(window).on('load', function (){
                     <h2>Sorry, this event isn't available for signup.</h2>
                 {% endif %}
                 <div>
-                    <a href="/event/{{ campaign.local_name }}/?akid={{args.akid}}&amp;zip={{args.zip}}">Search for another event</a>.
+                    <a href="/event/{{ search_campaign.local_name }}/?akid={{args.akid}}&amp;zip={{args.zip}}">Search for another event</a>.
                 </div>
 
                 <p>
diff --git a/db_templates/Original/event_search.html b/db_templates/Original/event_search.html
index 2678d8dc35..034164866e 100644
--- a/db_templates/Original/event_search.html
+++ b/db_templates/Original/event_search.html
@@ -62,7 +62,16 @@
                         </div>
                     {% endif %}
                     <button type="submit" class="ak-event-search">Search</button>
-                    
+                    {% if campaign.also_search_campaigns.all %}
+                    <div>
+                        <input type="hidden" name="also_search_present" value="1">
+                        Include:
+                        {% for also in campaign.also_search_campaigns.all %}
+                            <input type="checkbox" name="also_search" value="{{ also.name }}" checked>
+                            {{ also.title }}
+                        {% endfor %}
+                    </div>
+                    {% endif %}
                 </div>
             </div>
         </div>
diff --git a/db_templates/Original/event_search_results.html b/db_templates/Original/event_search_results.html
index ebf7d54064..b4330d144f 100644
--- a/db_templates/Original/event_search_results.html
+++ b/db_templates/Original/event_search_results.html
@@ -39,26 +39,22 @@
 					loc.push([ 
 							{% if event.is_in_past or event.is_full %}
 								'<p>{{ event.starts_at_dt|date:"l, F jS, G:i a" }}</p>' +
-								'<p class="ak-event-title"><a href="/event/{{ campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}"' +
-								{% if campaign.use_title and campaign.show_title and event.title %}
-								'>{{ event.title }}' + 
-								{% else %}
-								' class="ak-campaign-title">{{ campaign.local_title }}' + 
-								{% endif %} 
-								'</a></p>' +
+								'<p class="ak-event-title"><a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}"' +
+								{% if event.public_title_is_from_campaign %}
+								' class="ak-campaign-title">' + 
+								{% endif %}
+								'{{ event.public_title }}</a></p>' +
 								'<p>{{ event.address1 }} <br />{{ event.city }} {{ event.state }} {{ event.zip }} </p>' +
 								'{% if event.is_in_past %} <p><strong>Sorry, the event is over.</strong></p> {% endif %} {% if event.is_full %} <p><strong>Sorry, the event is full.</strong></p> {% endif %}'
 							{% else %}
 								'<p>{{ event.starts_at_dt|date:"l, F jS, G:i a" }}</p>' +
-								'<p class="ak-event-title"><a href="/event/{{ campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}"' +
-								{% if campaign.use_title and campaign.show_title and event.title %}
-								'>{{ event.title }}' + 
-								{% else %}
-								' class="ak-campaign-title">{{ campaign.local_title }}' + 
+								'<p class="ak-event-title"><a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}"' +
+								{% if event.public_title_is_from_campaign %}
+								' class="ak-campaign-title">' + 
 								{% endif %}
-								'</a></p>' +
+								'{{ event.public_title }}</a></p>' +
 								'<p>{{ event.address1 }} <br />{{ event.city }} {{ event.state }} {{ event.zip }} </p>' +
-								'<a href="/event/{{ campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}" class="ak-button">Sign up</a>'
+								'<a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}" class="ak-button">Sign up</a>'
 							{% endif %},
 							{{ event.latitude }},
 							{{ event.longitude }},
@@ -111,31 +107,28 @@
     	{% endif %}
 		
         {% for event in events %}   	
-          	
         	{% if event.is_in_past or event.is_full %}
         	
         	{% else %}
         	{% if not all %}
 				<div class="ak-field-box {% if event.is_in_past or event.is_full %}ak-event-disabled{% endif %}">
 					<div class="ak-info-column">
-					{% if campaign.use_title and campaign.show_title and event.title %}
-						<p class="ak-event-title"><a href="/event/{{ campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}">{{ event.title }}</a></p>
-					{% else %}
 						<p class="ak-event-title">
-							<a href="/event/{{ campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}" class="ak-campaign-title">{{ campaign.local_title }}</a>
+							<a href="/event/{{ event.campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;source={{args.source}}{% if campaign.id != event.campaign.id %}&amp;search_campaign={{ campaign.name }}{% endif %}" {% if event.public_title_is_from_campaign %}class="ak-campaign-title"{% endif %}>
+                                {{ event.public_title }}
+                             </a>
 						</p>
-					{% endif %}
-			
-					{% if campaign.show_venue and event.venue %}
+
+					{% if event.campaign.show_venue and event.venue %}
 						<span class="ak-event-venue">{{ event.venue }}</span>
 					{% endif %}
 			
-					{% if campaign.show_address1 %}
+					{% if event.campaign.show_address1 %}
 						<div class="ak-event-address1">{{ event.address1 }}</div>
 					{% endif %}
 
-					{% if campaign.show_city or campaign.show_state or campaign.show_zip %}
-						{% if campaign.show_zip %}
+					{% if event.campaign.show_city or event.campaign.show_state or event.campaign.show_zip %}
+						{% if event.campaign.show_zip %}
 							<div class="ak-event-city-etc">{{ event.city_etc }}</div>
 						{% else %}
 							<div class="ak-event-city-etc">{{ event.city_etc_no_postal }}</div>
@@ -153,13 +146,13 @@
 								<td>{{ event.starts_at_dt|date:"l, F jS, G:i a" }}</td>
 							</tr>
 						{% endif %}
-						{% if campaign.show_directions and event.directions %}
+						{% if event.campaign.show_directions and event.directions %}
 							<tr class="ak-event-directions">
 								<th>Directions:</th> 
 								<td>{{ event.directions }}</span></td>
 							</tr>
 						{% endif %}
-						{% if campaign.show_attendee_count %}
+						{% if event.campaign.show_attendee_count %}
 							<tr class="ak-event-attendee-count">
 								<th>Attendee count:</th>
 								<td>{{ event.attendee_count }} attendee{{ event.attendee_count|pluralize }}</td>
@@ -168,7 +161,7 @@
 					</table>
 					</div>
 					<div class="ak-description-column">
-					{% if campaign.show_public_description %}
+					{% if event.campaign.show_public_description %}
 						{% if event.public_description %}
 							<p class="ak-event-description">
 								{{ event.public_description }}

2.3.52 (2019 May 9)

Using a login string should never make the host manually log in

diff --git a/db_templates/Original/event_chooser.html b/db_templates/Original/event_chooser.html
index 301e747de5..7d4bb678c3 100644
--- a/db_templates/Original/event_chooser.html
+++ b/db_templates/Original/event_chooser.html
@@ -24,7 +24,7 @@
             <ul>
                 {% for signup in signups %}
                 <li>
-                    <a href="{{ signup.url }}">{{ signup.event.title }} &mdash; {{ signup.event.get_starts_at_display }}</a>
+                    <a href="{{ signup.url }}{% if signup.role == 'host' and request.GET.i %}&i={{ request.GET.i|urlencode }}&l=1{% endif %}">{{ signup.event.title }} &mdash; {{ signup.event.get_starts_at_display }}</a>
                     {% if signup.role == 'host' %}
                     &nbsp;(you are hosting)
                     {% endif %}

Add checkbox to suppress notification email on moderator deletion

diff --git a/db_templates/Original/event_moderate_tools.html b/db_templates/Original/event_moderate_tools.html
index 10cc1cf393..e625eaeb8d 100644
--- a/db_templates/Original/event_moderate_tools.html
+++ b/db_templates/Original/event_moderate_tools.html
@@ -140,6 +140,12 @@ $(initModeratorTools);
                {% include_tmpl form.custom_field_html %}
              </div>
              <div class="ak-limit-by-mark ak-deleted instructions">
+              <div>
+                  <label style="padding: 0; font-weight: normal">
+                      <input name="suppress_email" type="checkbox">
+                      Don't send notification emails for this change
+                  </label>
+              </div>
                <p>
                  Please explain why you're deleting this event e.g. spam, unresponsive host, etc.
                </p>

2.3.51 (2019 April 23)

Fix validation on recurring update page

diff --git a/db_templates/Original/recurring_info.html b/db_templates/Original/recurring_info.html
index bfefec95d4..8fae343126 100644
--- a/db_templates/Original/recurring_info.html
+++ b/db_templates/Original/recurring_info.html
@@ -1,12 +1,12 @@
 {% with profile.payment_processor_information as pp %}
-<form id="change_profile_{{ profile.id }}" {% if pp.processor == "stripe" %}data-stripe-pub-key="{{ pp.pub_key }}"{% endif %} class="action_form" name="act_{{ profile.id }}" method="POST" action="/act/" accept-charset="utf-8">
+<form id="change_profile_{{ profile.id }}" {% if pp.processor == "stripe" %}data-stripe-pub-key="{{ pp.pub_key }}"{% endif %} class="action_form" name="act_{{ profile.id }}" method="POST" action="/act/" accept-charset="utf-8" onsubmit="return true;">
     <input type="hidden" name="page" value="{{ page.name }}">
     <input type="hidden" name="profile_id" value="{{ profile.id }}">
     {% comment %} akid is needed to tell javascript required field checking we have a user, but the view requires a user to be logged in. {% endcomment %}
     <input type="hidden" name="akid" value="{{ logged_in_user.token }}">
 
     <div class="ak-field-box">
-        <div class="ak-styled-fields ak-labels-before {{templateset.custom_fields.field_errors_class|default:"ak-err-below"}}">
+        <div class="ak-styled-fields ak-labels-before {{templateset.custom_fields.field_errors_class|default:"ak-err-above"}}">
 {% once %}<ul id="ak-errors"></ul>{% endonce %}
           <ul class="ak-errors"></ul>
 
@@ -123,14 +123,16 @@
             <label for="ak-routing_number_{{ profile.id }}">
                 Routing Number
             </label>
-            <input id="ak-routing_number_{{ profile.id }}" type="text" name="routing_number" size="9" style="max-width: 33%" disabled=true>
+            <input id="ak-routing_number_{{ profile.id }}" type="text" name="routing_number" size="9" style="max-width: 33%" onvalidate="return valid_bank_routing_number(this.value)" disabled=true>
+            <input type="hidden" name="required" value="routing_number"/>
         </div>
 
         <div class="ak-change-cc" style="display: none">
             <label for="ak-bank_account_{{ profile.id }}">
                 Bank Account
             </label>
-            <input id="ak-bank_account_{{ profile.id }}" type="text" name="bank_account" size="17" style="max-width: 33%" disabled=true>
+            <input id="ak-bank_account_{{ profile.id }}" type="text" name="bank_account" size="17" style="max-width: 33%" onvalidate="return valid_bank_account_number(this.value)" disabled=true>
+            <input type="hidden" name="required" value="bank_account"/>
         </div>
 
         <div class="ak-change-cc" style="display: none">
@@ -147,7 +149,7 @@
             <label for="ak-ownership_type_{{ profile.id }}">
                 Ownership Type
             </label>
-            <select id="ak-ownership_type_{{ profile.id }}" type="text" name="ownership_type" style="max-width: 33%" disabled=true>
+            <select id="ak-ownership_type_{{ profile.id }}" type="text" name="ownership_type" style="max-width: 33%" onvalidate="return validate_business_name(this)" disabled=true>
               <option value="personal">personal</option>
               <option value="business">business</option>
             </select>
@@ -180,14 +182,19 @@
             {% if profile.payment_processor.recurring_update_supports_address %}
     
             <div class="ak-change-address" style="display: none">
+              {% if not profile.is_ach %}
                 <div style="text-align: center">
-                    Optionally also enter your {% if profile.is_ach %}bank account{% else %}credit card{% endif %} billing address:
+                    Optionally also enter your credit card billing address:
                 </div>
+              {% endif %}
                 <div>
                     <label for="id_address1_{{ profile.id }}">
                         Street Address
                     </label>
                     <input name="address1" id="id_address1_{{ profile.id }}" type="text" value="{{ profile.order.user_detail.address1 }}" disabled=true>
+                    {% if profile.is_ach %}
+                    <input type="hidden" name="required" value="address1"/>
+                    {% endif %}
                 </div>
     
                 <div>
@@ -195,6 +202,9 @@
                         City
                     </label>
                     <input name="city" id="id_city_{{ profile.id }}" type="text" value="{{ profile.order.user_detail.city }}" disabled=true>
+                    {% if profile.is_ach %}
+                    <input type="hidden" name="required" value="city"/>
+                    {% endif %}
                 </div>
         
                 <div class="state_select_box">
@@ -202,6 +212,9 @@
                         State
                     </label>
                     {% include "./state_select.html" %}
+                    {% if profile.is_ach %}
+                    <input type="hidden" name="required" value="state"/>
+                    {% endif %}
                 </div>
     
                 <div>
@@ -209,6 +222,9 @@
                         ZIP Code
                     </label>
                     <input name="zip" id="id_zip_{{ profile.id }}" type="text" value="{{ profile.order.user_detail.zip }}" disabled=true>
+                    {% if profile.is_ach %}
+                    <input type="hidden" name="required" value="zip"/>
+                    {% endif %}
                 </div>
                 {% if not profile.is_ach %}
                 <div>
@@ -229,19 +245,19 @@
                     <label>
                         Country
                     </label>
-                    {% include "./country_select.html" %}
+                    {% include "./country_select.html" with onchange="actionkit.forms.setForm(this.form); actionkit.forms.reflectCountryChange();" %}
                 </div>
                 {% endif %}
                 <script>
                   $(function () {
+                     var profile_el = $("#change_profile_{{ profile.id }}");
                      {% if profile.order.user_detail.country %}
-                       $("#change_profile_{{ profile.id }} .country_select_box select").val("{{ profile.order.user_detail.country }}");
+                       profile_el.find(".country_select_box select").val("{{ profile.order.user_detail.country }}");
                      {% endif %}
                      {% if profile.order.user_detail.state %}
-                       $("#change_profile_{{ profile.id }} .state_select_box select").val("{{ profile.order.user_detail.state }}");
+                       profile_el.find(".state_select_box select").val("{{ profile.order.user_detail.state }}");
                      {% endif %}
                      actionkit.forms.initForm('act_{{ profile.id }}');
-                     actionkit.forms.reflectCountryChange();
                   });
                 </script>
             </div>
@@ -256,7 +272,7 @@
         {% endif %}
 
         <div class="ak-change-submit ak-align-right" style="display: none">
-            <button name="submit_form" type="submit">Save Changes</button>
+            <button name="submit_form" type="submit" {% if profile.is_ach %}onclick="return ach_validation(this.form);"{% else %}onclick="return disable_invisibles(this.form);"{% endif %}">Save Changes</button>
         </div>
 
         </div>
diff --git a/db_templates/Original/recurring_update.html b/db_templates/Original/recurring_update.html
index cedeb45284..539d00cca1 100644
--- a/db_templates/Original/recurring_update.html
+++ b/db_templates/Original/recurring_update.html
@@ -236,61 +236,40 @@ function valid_bank_routing_number(value) {
     return true;
 }
 
-function validate_ach(form) {
-    var has_errors = false;
-
-    if (form['ownership_type'].value == 'business' && !form['business_name'].value) {
-        actionkit.errors['business_name:missing'] = actionkit.forms.errorMessage('business_name:missing');
-        has_errors = true;
-     }
-
-     var bank_account = form['bank_account'].value;
-     if (!bank_account) {
-         actionkit.errors['bank_account:missing'] = actionkit.forms.errorMessage('bank_account:missing');
-         has_errors = true;
-     } else if (!valid_bank_account_number(bank_account)) {
-         actionkit.errors['bank_account:invalid'] = actionkit.forms.errorMessage('bank_account:invalid');
-         has_errors = true;
-     }
-
-     var routing_number = form['routing_number'].value;
-     if (!routing_number) {
-         actionkit.errors['routing_number:missing'] = actionkit.forms.errorMessage('routing_number:missing');
-         has_errors = true;
-     } else if (!valid_bank_routing_number(routing_number)) {
-         actionkit.errors['routing_number:invalid'] = actionkit.forms.errorMessage('routing_number:invalid');
-         has_errors = true;
-     }
-
-     return has_errors;
+function validate_business_name(input) {
+    form = input.form;
+    if (input.value == 'business' && !form['business_name'].value) {
+        return "Business name is required for business accounts.";
+    }
+
+    return true;
 }
 
-$(function () {
-    $('button[type="submit"]').on('click', function() {
-        var has_errors = false;
-        var form = $(this).closest('form')[0];
-        actionkit.forms.setForm(form);
+function ach_validation(form) {
+    actionkit.forms.setForm(form);
 
-        if (!actionkit.errors) {
-            actionkit.errors = {};
-        }
+    // allow changing amount separately
+    if ($(form['bank_account']).is(':hidden')) { return true; }
 
-        if (form["payment_method"] && form["payment_method"].value == 'ach') {
-            // allow changing amount separately
-            if ($(form['bank_account']).is(':hidden')) { return true; }
-            has_errors = validate_ach(form);
-        }
+    // clear_errors will delete these inputs from other
+    // forms if we don't remove the error class from them here
+    $(':input.ak-error, label.ak-error').removeClass('ak-error');
 
-        if (has_errors) {
-            // clear_errors will delete these inputs from other
-            // forms if we don't remove the error class from them here
-            $(':input.ak-error, label.ak-error').removeClass('ak-error')
-            actionkit.forms.onValidationErrors(actionkit.errors);
-        }
+    // address is required for ACH changes, even for logged in users
+    var saved_state = actionkit.forms.alwaysRequireUserFields;
+    actionkit.forms.alwaysRequireUserFields = true;
+    var is_valid = actionkit.forms.validate();
+    actionkit.forms.alwaysRequireUserField = saved_state;
 
-        return !has_errors;
-    });
-});
+    return is_valid;
+}
+
+/* This prevents the invisible inputs from being submitted. Needed
+ because reflectCountryChange() messes with the disabled prop. */
+function disable_invisibles(form) {
+    $(form).find(':input:hidden[type!="hidden"]').prop('disabled', true);
+    return true;
+}
 </script>
 
 {% endblock %}

2.3.50 (2019 March 27)

The user_update.html template should include state_select.html

diff --git a/db_templates/Original/user_update.html b/db_templates/Original/user_update.html
index 12cd745a04..d0fc420a41 100644
--- a/db_templates/Original/user_update.html
+++ b/db_templates/Original/user_update.html
@@ -46,61 +46,13 @@
                                 {% endfor %}
                             </select>
                         {% case 'state' %} 
-                                                       <select name="{{ input_name_prefix }}state" id="id_{{ input_name_prefix }}state" {% if onchange %}onchange="{{ onchange }}" onblur="{{ onchange }}"{% endif %}>
-                                                       <option selected="{{ field.value }}">{{ field.value }}</option>
-                                                       <option value="AL">Alabama</option>
-                                                               <option value="AK">Alaska</option>
-                                                               <option value="AZ">Arizona</option>
-                                                               <option value="AR">Arkansas</option>
-                                                               <option value="CA">California</option>
-                                                               <option value="CO">Colorado</option>
-                                                               <option value="CT">Connecticut</option>
-                                                               <option value="DE">Delaware</option>
-                                                               <option value="DC">District of Columbia</option>
-                                                               <option value="FL">Florida</option>
-                                                               <option value="GA">Georgia</option>
-                                                               <option value="HI">Hawaii</option>
-                                                               <option value="ID">Idaho</option>
-                                                               <option value="IL">Illinois</option>
-                                                               <option value="IN">Indiana</option>
-                                                               <option value="IA">Iowa</option>
-                                                               <option value="KS">Kansas</option>
-                                                               <option value="KY">Kentucky</option>
-                                                               <option value="LA">Louisiana</option>
-                                                               <option value="ME">Maine</option>
-                                                               <option value="MD">Maryland</option>
-                                                               <option value="MA">Massachusetts</option>
-                                                               <option value="MI">Michigan</option>
-                                                               <option value="MN">Minnesota</option>
-                                                               <option value="MS">Mississippi</option>
-                                                               <option value="MO">Missouri</option>
-                                                               <option value="MT">Montana</option>
-                                                               <option value="NE">Nebraska</option>
-                                                               <option value="NV">Nevada</option>
-                                                               <option value="NH">New Hampshire</option>
-                                                               <option value="NJ">New Jersey</option>
-                                                               <option value="NM">New Mexico</option>
-                                                               <option value="NY">New York</option>
-                                                               <option value="NC">North Carolina</option>
-                                                               <option value="ND">North Dakota</option>
-                                                               <option value="OH">Ohio</option>
-                                                               <option value="OK">Oklahoma</option>
-                                                               <option value="OR">Oregon</option>
-                                                               <option value="PA">Pennsylvania</option>
-                                                               <option value="RI">Rhode Island</option>
-                                                               <option value="SC">South Carolina</option>
-                                                               <option value="SD">South Dakota</option>
-                                                               <option value="TN">Tennessee</option>
-                                                               <option value="TX">Texas</option>
-                                                               <option value="UT">Utah</option>
-                                                               <option value="VT">Vermont</option>
-                                                               <option value="VA">Virginia</option>
-                                                               <option value="WA">Washington</option>
-                                                               <option value="DC">Washington, D.C.</option>
-                                                               <option value="WV">West Virginia</option>
-                                                               <option value="WI">Wisconsin</option>
-                                                               <option value="WY">Wyoming</option>
-                                                       </select>
+                                <script>
+                                    $(function() { //Make sure the current State value is selected
+                                        let selectedState = "{{ field.value }}";
+                                        $('#id_state > option[value=' + selectedState + ']').prop('selected', true);
+                                    })
+                                </script>
+                                           {% include "./state_select.html" %}
 
                         {% else %}
                                 {{ field }}

2.3.49 (2019 March 7)

Language-picker template should use canonical paths to action pages, part 1

diff --git a/db_templates/Original/language_picker.html b/db_templates/Original/language_picker.html
index 3ea2a3a54a..64b0b2f00d 100644
--- a/db_templates/Original/language_picker.html
+++ b/db_templates/Original/language_picker.html
@@ -7,7 +7,7 @@
                 {% if translated_page.id == page.id %}
                     <span class="disabled-lang">{{ translated_page.lang.name|default:"English" }}</span>
                 {% else %}
-                    <a class="lang" href="/act/{{ translated_page.name }}/">{{ translated_page.lang.name|default:"English" }}</a>
+                    <a class="lang" href="{{ translated_page.derived.canonical_path }}">{{ translated_page.lang.name|default:"English" }}</a>
                 {% endif %}
                 {% if not forloop.last %}
                     &bull;

Language-picker template should use canonical paths to action pages, part 2

diff --git a/db_templates/Original (before Fall 2016)/language_picker.html b/db_templates/Original (before Fall 2016)/language_picker.html
index 301fe994f4..9f986bc416 100644
--- a/db_templates/Original (before Fall 2016)/language_picker.html     
+++ b/db_templates/Original (before Fall 2016)/language_picker.html     
@@ -7,7 +7,7 @@
     {% ifequal translated_page.id page.id %}
     <span class="disabled-lang">{{ translated_page.lang.name|default:"English" }}</span>
     {% else %}
-    <a class="lang" href="/act/{{ translated_page.name }}/">{{ translated_page.lang.name|default:"English" }}</a>
+    <a class="lang" href="{{ translated_page.derived.canonical_path }}">{{ translated_page.lang.name|default:"English" }}</a>
     {% endifequal %}
     {% if not forloop.last %}
     &bull;

Make user-facing recurring update forms work for ACH orders

diff --git a/db_templates/Original/recurring_info.html b/db_templates/Original/recurring_info.html
index d128bf97e6..bfefec95d4 100644
--- a/db_templates/Original/recurring_info.html
+++ b/db_templates/Original/recurring_info.html
@@ -1,13 +1,15 @@
 {% with profile.payment_processor_information as pp %}
-<form id="change_profile_{{ profile.id }}" {% if pp.processor == "stripe" %}data-stripe-pub-key="{{ pp.pub_key }}"{% endif %} class="action_form" name="act" method="POST" action="/act/" accept-charset="utf-8">
+<form id="change_profile_{{ profile.id }}" {% if pp.processor == "stripe" %}data-stripe-pub-key="{{ pp.pub_key }}"{% endif %} class="action_form" name="act_{{ profile.id }}" method="POST" action="/act/" accept-charset="utf-8">
     <input type="hidden" name="page" value="{{ page.name }}">
     <input type="hidden" name="profile_id" value="{{ profile.id }}">
     {% comment %} akid is needed to tell javascript required field checking we have a user, but the view requires a user to be logged in. {% endcomment %}
     <input type="hidden" name="akid" value="{{ logged_in_user.token }}">
 
     <div class="ak-field-box">
-        <div class="ak-styled-fields ak-labels-before {{templateset.custom_fields.field_errors_class|default:"ak-errs-below"}}">
-        
+        <div class="ak-styled-fields ak-labels-before {{templateset.custom_fields.field_errors_class|default:"ak-err-below"}}">
+{% once %}<ul id="ak-errors"></ul>{% endonce %}
+          <ul class="ak-errors"></ul>
+
         <div>
             <label>
                 Next Payment 
@@ -95,8 +97,72 @@
         </div>
 
         {% endif %}
+
+        {% if profile.is_ach %}
+        <input type="hidden" name="payment_method" value="ach">
+        <input type="hidden" name="first_name" value="{{ user.first_name }}">
+        <input type="hidden" name="last_name" value="{{ user.last_name }}">
         
-        {% if profile.order.payment_method == "cc" %}
+        <div class="ak-show-cc">
+            <label>
+                Bank Account
+            </label>
+            <div class="ak-readonly-value">
+                <div>
+                  {% if profile.card_num %}
+                  Account ending in {{ profile.card_num }}
+                  {% endif %}
+                </div>
+                <div>
+                   {%if not profile.is_import_stub %} <a href="#" onclick="return ak_recurring_change_card('{{ profile.id }}');">Change bank information</a>{% endif %}
+                </div>
+            </div>
+        </div>
+
+        <div class="ak-change-cc" style="display: none">
+            <label for="ak-routing_number_{{ profile.id }}">
+                Routing Number
+            </label>
+            <input id="ak-routing_number_{{ profile.id }}" type="text" name="routing_number" size="9" style="max-width: 33%" disabled=true>
+        </div>
+
+        <div class="ak-change-cc" style="display: none">
+            <label for="ak-bank_account_{{ profile.id }}">
+                Bank Account
+            </label>
+            <input id="ak-bank_account_{{ profile.id }}" type="text" name="bank_account" size="17" style="max-width: 33%" disabled=true>
+        </div>
+
+        <div class="ak-change-cc" style="display: none">
+            <label for="ak-account_type_{{ profile.id }}">
+                Account Type
+            </label>
+            <select id="ak-account_type_{{ profile.id }}" type="text" name="account_type" style="max-width: 33%" disabled=true>
+              <option value="checking">checking</option>
+              <option value="savings">savings</option>
+            </select>
+        </div>
+
+        <div class="ak-change-cc" style="display: none">
+            <label for="ak-ownership_type_{{ profile.id }}">
+                Ownership Type
+            </label>
+            <select id="ak-ownership_type_{{ profile.id }}" type="text" name="ownership_type" style="max-width: 33%" disabled=true>
+              <option value="personal">personal</option>
+              <option value="business">business</option>
+            </select>
+        </div>
+
+        <div class="ak-change-cc ak-business_name" style="display: none">
+            <label for="ak-business_name_{{ profile.id }}">
+                Business Name
+            </label>
+            <input id="ak-business_name_{{ profile.id }}" type="text" name="business_name" disabled=true>
+        </div>
+
+        {% endif %}
+
+        {% if profile.order.payment_method == "cc" or profile.is_ach %}
             <div class="ak-show-address" style="display: none">
                 <label>
                     Address
@@ -115,7 +181,7 @@
     
             <div class="ak-change-address" style="display: none">
                 <div style="text-align: center">
-                    Optionally also enter your credit card billing address:
+                    Optionally also enter your {% if profile.is_ach %}bank account{% else %}credit card{% endif %} billing address:
                 </div>
                 <div>
                     <label for="id_address1_{{ profile.id }}">
@@ -144,7 +210,7 @@
                     </label>
                     <input name="zip" id="id_zip_{{ profile.id }}" type="text" value="{{ profile.order.user_detail.zip }}" disabled=true>
                 </div>
-    
+                {% if not profile.is_ach %}
                 <div>
                     <label for="id_region_{{ profile.id }}">
                         Region
@@ -165,7 +231,7 @@
                     </label>
                     {% include "./country_select.html" %}
                 </div>
-
+                {% endif %}
                 <script>
                   $(function () {
                      {% if profile.order.user_detail.country %}
@@ -174,6 +240,7 @@
                      {% if profile.order.user_detail.state %}
                        $("#change_profile_{{ profile.id }} .state_select_box select").val("{{ profile.order.user_detail.state }}");
                      {% endif %}
+                     actionkit.forms.initForm('act_{{ profile.id }}');
                      actionkit.forms.reflectCountryChange();
                   });
                 </script>
@@ -182,6 +249,11 @@
             {% endif %}
         {% endif %}
 
+        {% if profile.is_ach %}
+        <div class="ak-change-cc" style="display: none">
+          <div class="ak-mandate">By clicking "Save Changes", I authorize Braintree, a service of PayPal, on behalf of <span class="ak-client-name">{% filter ak_text:"org_name" %}{% client_name %}{% endfilter %}</span> (i) to verify my bank account information using bank information and consumer reports and (ii) to debit my bank account.</div>
+        </div>
+        {% endif %}
 
         <div class="ak-change-submit ak-align-right" style="display: none">
             <button name="submit_form" type="submit">Save Changes</button>
@@ -191,9 +263,20 @@
     </div>
 
 </form>
-{% if pp.use_vzero and profile.order.payment_method == "cc" %}
+{% if pp.use_vzero %}
 <script>
-initVZeroForForm('#change_profile_{{ profile.id }}', '{{ pp.client_token }}');
+initVZeroForForm('#change_profile_{{ profile.id }}', '{{ pp.client_token }}', {% if profile.is_ach %}true{% else %}false{% endif %});
+
+var ownership_type_menu = $('#change_profile_{{ profile.id }} [name=ownership_type]');
+if (ownership_type_menu.length) {
+    ownership_type_menu.change(function() {
+        if (this.value == 'business') {
+            $(this.form).find('.ak-business_name').show();
+        } else {
+            $(this.form).find('.ak-business_name').hide();
+        }
+    });
+}
 </script>
 {% endif %}
 
diff --git a/db_templates/Original/recurring_update.html b/db_templates/Original/recurring_update.html
index 2a25085649..8b93c71e30 100644
--- a/db_templates/Original/recurring_update.html
+++ b/db_templates/Original/recurring_update.html
@@ -37,7 +37,7 @@
     function ak_recurring_change_card(profile_id) {
         var profile_el = $('#change_profile_' + profile_id);
         profile_el.find('.ak-show-cc').hide();
-        profile_el.find('.ak-change-cc').fadeIn();
+        profile_el.find('.ak-change-cc').not('.ak-business_name').fadeIn();
         profile_el.find('.ak-change-address').fadeIn();
         profile_el.find('.ak-change-submit').fadeIn();
         profile_el.find(':input').prop('disabled', false);
@@ -56,10 +56,11 @@
     $(document).ready(function() {
         var match = /profile_id=(\d+)/.exec(window.location.search);
         if (match) {
-            profile_id = match[1];
+            var profile_id = match[1];
             if (/error_card_num/.test(window.location.search) ||
-                    /error_address1/.test(window.location.search) ||
-                    /error_city/.test(window.location.search)) {
+                /error_address1/.test(window.location.search) ||
+                /error_city/.test(window.location.search) ||
+                /error_profile_id/.test(window.location.search)) {
                 ak_recurring_change_card(profile_id);
             } else if (/amount=/.test(window.location.search)) {
                 ak_recurring_change_amount(profile_id);
@@ -74,7 +75,7 @@
 {% braintree_js_libs %}
 <script src="/resources/ak_braintree_vzero.js"></script>
 <script>
-function initVZeroForForm(form_id, clientToken) {
+function initVZeroForForm(form_id, clientToken, is_ach) {
     var form = document.querySelector(form_id),
         options = {
             form: form,
@@ -111,13 +112,18 @@ function initVZeroForForm(form_id, clientToken) {
         toRemove = ["input[name=card_num]", "input[name=card_code]",
                     "input[name=exp_date]"];
 
-    toRemove.forEach(function(el) {
-        form.querySelector(el).remove();
-    });
-    Object.keys(options.fields).forEach(function(key) {
-        var field = options.fields[key];
-        document.querySelector(field.selector).classList.add('hosted-field');
-    });
+    if (is_ach) {
+        options['fields'] = {};
+        options['ach'] = true;
+    } else {
+        toRemove.forEach(function(el) {
+            form.querySelector(el).remove();
+        });
+        Object.keys(options.fields).forEach(function(key) {
+            var field = options.fields[key];
+            document.querySelector(field.selector).classList.add('hosted-field');
+        });
+    }
 
     actionkit.donations.initClient(clientToken, options);
 }
@@ -190,6 +196,85 @@ function onStripeResponse(status, response) {
 {% endif %}
 {% endfor %}
 
+<script type="text/javascript">
+function valid_bank_account_number(value) {
+    return /^\d{4,17}$/.test(value);
+}
+
+function valid_bank_routing_number(value) {
+    value = value.replace(/\D/g,'');
+
+    if (value.length != 9) { return false; }
+
+    var checksum = 0;
+    for (var i = 0; i < value.length; i += 3) {
+        checksum += parseInt(value.charAt(i), 10) * 3
+                 +  parseInt(value.charAt(i + 1), 10) * 7
+                 +  parseInt(value.charAt(i + 2), 10);
+    }
+
+    if (checksum == 0 || checksum % 10 != 0) { return false; }
+
+    return true;
+}
+
+function validate_ach(form) {
+    var has_errors = false;
+
+    if (form['ownership_type'].value == 'business' && !form['business_name'].value) {
+        actionkit.errors['business_name:missing'] = actionkit.forms.errorMessage('business_name:missing');
+        has_errors = true;
+     }
+
+     var bank_account = form['bank_account'].value;
+     if (!bank_account) {
+         actionkit.errors['bank_account:missing'] = actionkit.forms.errorMessage('bank_account:missing');
+         has_errors = true;
+     } else if (!valid_bank_account_number(bank_account)) {
+         actionkit.errors['bank_account:invalid'] = actionkit.forms.errorMessage('bank_account:invalid');
+         has_errors = true;
+     }
+
+     var routing_number = form['routing_number'].value;
+     if (!routing_number) {
+         actionkit.errors['routing_number:missing'] = actionkit.forms.errorMessage('routing_number:missing');
+         has_errors = true;
+     } else if (!valid_bank_routing_number(routing_number)) {
+         actionkit.errors['routing_number:invalid'] = actionkit.forms.errorMessage('routing_number:invalid');
+         has_errors = true;
+     }
+
+     return has_errors;
+}
+
+$(function () {
+    $('button[type="submit"]').on('click', function() {
+        var has_errors = false;
+        var form = $(this).closest('form')[0];
+        actionkit.forms.setForm(form);
+
+        if (!actionkit.errors) {
+            actionkit.errors = {};
+        }
+
+        if (form["payment_method"] && form["payment_method"].value == 'ach') {
+            // allow changing amount separately
+            if ($(form['bank_account']).is(':hidden')) { return true; }
+            has_errors = validate_ach(form);
+        }
+
+        if (has_errors) {
+            // clear_errors will delete these inputs from other
+            // forms if we don't remove the error class from them here
+            $(':input.ak-error, label.ak-error').removeClass('ak-error')
+            actionkit.forms.onValidationErrors(actionkit.errors);
+        }
+
+        return !has_errors;
+    });
+});
+</script>
+
 {% endblock %}
 
 {% block content %}

Ensure commas in donation amount don't fail JS validation

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index 3aa9c3c0aa..42ac476232 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -624,11 +624,11 @@
         $('.ak_candidate_inputs').each(function (i) {
             // check that amount is a valid amount
             var amount = this.value;
-            if (amount.length == 0 || ! /^[0-9]\d*(\.\d\d)?$/.test(amount)) {
+            if (amount.length == 0 || ! /^[\d,]*(\.\d{1,2})?$/.test(amount)) {
                 return;
             }
 
-            total += parseFloat(amount);
+            total += parseFloat(amount.replace(',', ''));
         });
 
         // look for checked off donation amounts
@@ -639,8 +639,8 @@
             // or other amounts
             var other = $('#ak-other-amount-field').val();
             if (typeof other !== "undefined" &&
-                other.length && /^\d*(\.\d{1,2})?$/.test(other))
-                total += parseFloat(other);
+                other.length && /^[\d,]*(\.\d{1,2})?$/.test(other))
+                total += parseFloat(other.replace(',', ''));
         }
 
         var new_total = total == 0 ? "" : "" + total.toFixed(2);
@@ -1042,7 +1042,7 @@
             
         } else if (($('#ak-other-amount-field').val() != "" &&
                     $('#ak-other-amount-field').val() != undefined)  &&
-                   !/^\d*(\.\d{1,2})?$/.test($('#ak-other-amount-field').val())) {
+                   !/^[\d,]*(\.\d{1,2})?$/.test($('#ak-other-amount-field').val())) {
             if (!actionkit.errors)
                 actionkit.errors = {};
             actionkit.errors['amount:invalid'] = actionkit.forms.errorMessage('amount:invalid');

2.3.48 (2019 Feb 19)

No changes.

2.3.47 (2019 Jan 30)

Fix Incorrect style used for email attendees confirmation message

index 961cf4705..fb0fa1bb3 100644
--- a/db_templates/Original/event_roster.html
+++ b/db_templates/Original/event_roster.html
@@ -12,7 +12,7 @@
         {% if signups.role == 'host' %}{% if user_is_moderator %}Host{% else %}Cohost{% endif %}{% else %}Attendee{% endif %}{{ signups|length|pluralize }} ({{ signups|length }}  total)
     </h3>
     <form id="manage-{{signups.role}}" onvalidate="return validateRoster(this)" onconfirm="return confirmRoster(this)" name="manage-{{signups.role}}" method="post" action="/event/{{campaign.local_name}}/{{event.id}}/manage_signups" accept-charset="utf-8">
-
+        <div id="manage-{{signups.role}}-confirmation" class="ak-confirmation"></div>
         <ul class="compact" id="ak-errors"></ul>
 
         <input type="hidden" name="page" value="{{ page.name }}">

Fix Event edit has an empty required email field

diff --git a/db_templates/Original/event_create.html b/db_templates/Original/event_create.html
index f5bbf5755..283941ec1 100644
--- a/db_templates/Original/event_create.html
+++ b/db_templates/Original/event_create.html
@@ -4,6 +4,9 @@
 {% block content %}
 
 <form class="ak-form norecognize" name="act" method="POST" action="{% if update %}/update_action/{% else %}/act/{% endif %}" accept-charset="utf-8">
+    {% if args.want_prefill_data %}
+    <input type="hidden" name="want_prefill_data" value="1">
+    {% endif %}
     {% csrf_token %}
     <input type="hidden" name="page" value="{{ page.name }}">
 

2.3.46 (2019 Jan 9)

ACH: mark first_name / last_name required and remove name required on page

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index 6fc864730..3aa9c3c0a 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -253,10 +253,12 @@
                         <div>
                             <label for="id_first_name">First Name</label>
                             <input id="id_first_name" type="text" name="first_name">
+                            <input type="hidden" name="required" value="first_name">
                         </div>
                         <div>
                             <label for="id_last_name">Last Name</label>
                             <input id="id_last_name" type="text" name="last_name">
+                            <input type="hidden" name="required" value="last_name">
                         </div>
                       {% else %}
                         <div>

2.3.45 (2018 November 28)

No changes.

2.3.44 (2018 October 30)

No changes.

2.3.43 (2018 October 10)

No changes.

2.3.42 (2018 September 26)

Added more options for event moderation to support workflows beyond asking moderators to simply approve events.

diff --git a/db_templates/Original/event_moderate_tools.html b/db_templates/Original/event_moderate_tools.html
index c20d3c4ab..10cc1cf39 100644
--- a/db_templates/Original/event_moderate_tools.html
+++ b/db_templates/Original/event_moderate_tools.html
@@ -38,7 +38,8 @@ function updateFormClasses($field){
 function initModeratorTools(){
   $("#event-moderate-tools .jump-link").click(handleModeratorJumpLink);
   $("#id_mark_as").change(function(e){updateFormClasses($(e.target))});
-  $("tbody.email-row").click(function(e){$(e.currentTarget).toggleClass("ak-collapse-toggle-inactive");});
+  $("tr.ak-custom-fields").has("li").closest("tbody").addClass("ak-collapse");
+  $(".ak-collapse").click(function(e){$(e.currentTarget).toggleClass("ak-collapse-toggle-inactive")});
   $("button[confirm-message]").click(confirmSubmit);
   actionkit.forms.setForm("act");
 }
@@ -64,6 +65,11 @@ $(initModeratorTools);
              {% if campaign.require_email_confirmation and not event.host_is_confirmed %}
                 <li><span class="ak-error">This event is awaiting confirmation by the host</span><br><a onclick="$.get('/event/{{campaign.local_name}}/{{event.id}}/confirm_event/'); $(this).text('Confirmed');" href="#">Confirm for host</a></li>
              {% endif %}
+             {% if event.campaign.allow_moderator_edits and hosts %}
+                {% with host=hosts.0 %}
+                   <li><a href="/event/{{event.local_campaign.create_page.name}}/create/?action_id={{ host.action.id }}&amp;update=1&amp;want_prefill_data=1&akid={{ host.user.akid }}">Edit event details</a></li>
+                {% endwith %}
+             {% endif %}
              <li><a class="jump-link" id="email-hosts-link" href="#contact-hosts">Email host{{ hosts|length|pluralize }}</a></li>
              <li><a href="/logout/">Log out</a></li>
            </ul>
@@ -90,6 +96,22 @@ $(initModeratorTools);
               {% endif %}
           </ul>
       </div>
+      {% endblock %}
+      {% block details %}
+         <!-- Details -->
+         <div id="moderator-event-details ak-clearfix">
+             <h3>
+                 Event Details
+                 {% block event_edit_link %}
+                     {% if event.campaign.allow_moderator_edits and hosts %}
+                         {% with host=hosts.0 %}
+                             (<a class="ak-underline-on-hover" href="/event/{{event.local_campaign.create_page.name}}/create/?action_id={{ host.action.id }}&amp;update=1&amp;want_prefill_data=1&akid={{ host.user.akid }}">Edit</a>)
+                         {% endwith %}
+                     {% endif %}
+                 {% endblock %}
+             </h3>
+             {% include "./event_moderate_details.html" %}
+         </div>
      {% endblock %}
      {% block tools %}
      {% if not event.is_in_past %}
@@ -103,10 +125,10 @@ $(initModeratorTools);
 		<label for="id_mark_as">Mark event:</label>
 		<select name="event_mark_as" id="id_mark_as">
 		  <option value="">--</option>
-		  {% if event.is_awaiting_approval %}
+		  {% if event.is_awaiting_approval or event.status = 'flagged' %}
 		    <option value="approved">Approved</option>
 		  {% endif %}
-		  <option value="flagged">Incomplete</option>
+		  <option value="flagged">Flagged</option>
 		  <option value="deleted">Deleted</option>
 		</select>
 	      </div>
@@ -136,62 +158,78 @@ $(initModeratorTools);
 	  </div>
 	</form>
       {% endif %}
-        {% with previous_actions=event.moderation_actions  %}
-            {% if previous_actions %}
-            <div class="ak-clearfix">
-                <h3>
-                    Previous Moderator Activity
-                </h3>
-                <table class="moderation-tbl ak-data-table">
-                  <thead>
-		    <tr>
-                      <th class="date-col">Date</th>
-                      <th class="who-col">Who</th>
-                      <th class="comments-col">Comments</th>
-                      <th class="change-col"></th>
-		    </tr>
-                  </thead>
-                    <tbody>
-                      {% for action in previous_actions %}
- 		          <tr>
-                            <td>
-			      <p>
-				{{ action.get_created_at_display }}
-                              </p>
-                            </td>
-                            <td>
-                            <p>
-			      {% if action.user = signup.user and not user_is_manager %}
-                              You
-                              {% else %}
-                                {% if action.user.first_name %}
-                                   {{ action.user.first_name }} (Moderator)
-                                {% else %}
-                                   Another Moderator
-                                {% endif %}
-                              {% endif %}
-                            </p>
-                          </td>
-                          <td>
-			    <p>{{ action.custom_fields.comment }}</p>
-			  </td>
-			  <td>
-			      <p class="ak-status-changed">
-			        <b>
-                                {% if action.approved_event %}
-                                    APPROVED
-                                {% elif action.deleted_event %}
-                                    DELETED
-                                {% else %}
-                                    MARKED INCOMPLETE
-                                {% endif %}
-			        </b>
-			      </p>
-			  </td>
-			 </tr>
-		      {% endfor %}
-		    </tbody>
-                </table>
+{% with previous_actions=event.moderation_actions  %}
+{% if previous_actions %}
+<div class="ak-clearfix">
+    <h3>
+        Previous Moderator Activity
+    </h3>
+    <table class="moderation-tbl ak-data-table">
+        <thead>
+            <tr>
+                <th class="date-col">Date</th>
+                <th class="who-col">Who</th>
+                <th class="comments-col">Comments</th>
+                <th class="change-col" colspan="2">Change</th>
+            </tr>
+        </thead>
+        {% for action in previous_actions %}
+        <tbody class="ak-action-row ak-collapse-toggle-inactive">
+            <tr class="ak-basics">
+                <td>
+                    <p>
+                        {{ action.get_created_at_display }}
+                    </p>
+                </td>
+                <td>
+                    <p>
+                        {% if action.user = signup.user and not user_is_manager %}
+                          You
+                        {% else %}
+                          {% if action.user.first_name %}
+                           {{ action.user.first_name }} (Moderator)
+                          {% else %}
+                           Another Moderator
+                          {% endif %}
+                        {% endif %}
+                    </p>
+                </td>
+                <td>
+                    <p>{{ action.custom_fields.comment }}</p>
+                </td>
+                <td>
+                    <p class="ak-status-changed">
+                        <b>
+                            {% if action.approved_event %}
+                              APPROVED
+                            {% elif action.deleted_event %}
+                              DELETED
+                            {% else %}
+                              FLAGGED
+                            {% endif %}
+                        </b>
+                    </p>
+                </td>
+		<td class="ak-toggle-cell">
+		    <span class="ak-collapse-toggle-wrapper"><span class="ak-collapse-toggle-icon" >▼</span></span>
+		</td>
+            </tr>
+            <tr class="ak-custom-fields">
+                <td colspan="5">
+		    <ul>
+                        {% for key, value in action.custom_fields.items %}
+                           {% if key != "comment" %}
+                            <li>
+			     <b>{{ key|readable_identifier }}:</b> {{ value }}<br>
+			    </li>
+                           {% endif %}
+                        {% endfor %}
+		    </ul>
+                </td>
+            </tr>
+        </tbody>
+        {% endfor %}
+    </table>
             </div>
             {% endif %}
 	{% endwith %}
@@ -200,7 +238,7 @@ $(initModeratorTools);
               <h3>
                 Emails
               </h3>
-              <table class="moderation-tbl ak-data-table">
+              <table class="email-tbl ak-data-table">
                 <thead>
 		  <tr>
                     <th class="date-col">Date</th>
@@ -256,7 +294,6 @@ $(initModeratorTools);
             {% include "./event_roster.html" %}
           {% endwith %}
 	{% endif %}
-	{% block event_edit_link %}{% endblock %}
         {% include './event_roster_add.html' %}
 {% endblock %}
 {% block find_another_event %}<div><a href="/event/{{ campaign.local_name }}/moderator_search/">Search for more events</a></div>{% endblock %}
diff --git a/db_templates/Original/event_search_moderator.html b/db_templates/Original/event_search_moderator.html
index 9c4c9ed87..4c77d47ff 100644
--- a/db_templates/Original/event_search_moderator.html
+++ b/db_templates/Original/event_search_moderator.html
@@ -111,6 +111,10 @@
                        <input type="radio" name="status" {% if not campaign.require_staff_approval %}checked{% endif %} value="open"  id="id_open">
                        <label for="id_open">Open for signup</label>
                    </li>
+                   <li>
+                       <input type="radio" name="status" value="flagged" id="id_flagged">
+                       <label for="id_flagged">Flagged by moderator</label>
+                   </li>
                    <li>
                        <input type="radio" name="status" value="unconfirmed" id="id_unconfirmed">
                        <label for="id_unconfirmed">Waiting for host to confirm</label>
@@ -265,7 +269,7 @@
               </p>
             </div>
             [% } %]
-	    [% if(status == 'active'){ %]
+	    [% if(status == 'active' || status == 'flagged'){ %]
               <div>
                 <p>
                   <span class="ak-event-field ak-url">
diff --git a/db_templates/Original/event_host_tools.html b/db_templates/Original/event_host_tools.html
index 8c25c38c8..8f7d85967 100644
--- a/db_templates/Original/event_host_tools.html
+++ b/db_templates/Original/event_host_tools.html
@@ -211,14 +211,16 @@
         </div><!-- span -->
 
         <div class="ak-grid-col ak-grid-col-8-of-12">
-            <!-- Details -->
-            <div id="host-event-details ak-clearfix">
-                <h3>
-                    Event Details 
-                    {% block event_edit_link %}(<a class="ak-underline-on-hover" href="../../create/?action_id={{ action.id }}&amp;update=1&amp;want_prefill_data=1">Edit</a>){% endblock %}
-                </h3>
-                {% include "./event_host_details.html" %}
-            </div>
+            {% block details %}
+               <!-- Details -->
+               <div id="host-event-details ak-clearfix">
+                   <h3>
+                       Event Details
+                       {% block event_edit_link %}(<a class="ak-underline-on-hover" href="../../create/?action_id={{ action.id }}&amp;update=1&amp;want_prefill_data=1">Edit</a>){% endblock %}
+                   </h3>
+                   {% include "./event_host_details.html" %}
+               </div>
+            {% endblock %}
 	    {% block tools %}
                <!-- Cohost roster -->
                {% if cohosts %}

2.3.40 (2018 September 11)

Formatting changes to Event moderation pages

diff --git a/db_templates/Original/event_moderate_tools.html b/db_templates/Original/event_moderate_tools.html
index 1c3b0f7b9..c20d3c4ab 100644
--- a/db_templates/Original/event_moderate_tools.html
+++ b/db_templates/Original/event_moderate_tools.html
@@ -59,15 +59,14 @@ $(initModeratorTools);
            {% if event.is_awaiting_approval %}
               <span class="ak-error">This event is awaiting approval.</span>
            {% endif %}
-          <ul id="event-moderate-links">
-		  {% include_tmpl form.tools_sidebar %}
-                  {% if campaign.require_email_confirmation and not event.host_is_confirmed %}
-		     <br><span class="ak-error">This event is awaiting confirmation by the host</span><br><a onclick="$.get('/event/{{campaign.local_name}}/{{event.id}}/confirm_event/'); $(this).text('Confirmed');" href="#">Confirm for host</a><br>
-		  {% endif %}
-		  <a class="jump-link" id="email-hosts-link" href="#contact-hosts">Email host{{ hosts|length|pluralize }}</a>
-             </li>
+           <ul id="event-moderate-links">
+             {% include_tmpl form.tools_sidebar %}
+             {% if campaign.require_email_confirmation and not event.host_is_confirmed %}
+                <li><span class="ak-error">This event is awaiting confirmation by the host</span><br><a onclick="$.get('/event/{{campaign.local_name}}/{{event.id}}/confirm_event/'); $(this).text('Confirmed');" href="#">Confirm for host</a></li>
+             {% endif %}
+             <li><a class="jump-link" id="email-hosts-link" href="#contact-hosts">Email host{{ hosts|length|pluralize }}</a></li>
              <li><a href="/logout/">Log out</a></li>
-          </ul>
+           </ul>
       </div>
       {% endblock %}
       {% block host_links %}
diff --git a/db_templates/Original/event_search_moderator.html b/db_templates/Original/event_search_moderator.html
index 7805ad209..9c4c9ed87 100644
--- a/db_templates/Original/event_search_moderator.html
+++ b/db_templates/Original/event_search_moderator.html
@@ -6,9 +6,6 @@
 
 <script type='text/javascript'>
  $(window).load(function() {
-    $("button.ak-moderator-search-default").click(function(e){
-        $("#ak_advanced_event_search").submit();
-    });
     $("#ak_advanced_event_search").submit(function(){
         $("#ak-search-spinner").show();
         $("#event-count").closest("div").hide();
@@ -58,7 +55,6 @@
     </div>
     {% endif %}
     <p>
-	<button class="ak-event-search ak-moderator-search-default">Show events</button>
 	<h4 id="ak-advanced-toggle" class="ak-advanced-toggle ak-collapse ak-collapse-toggle-inactive">
 	    Advanced Search
 	    <span class="ak-collapse-toggle-wrapper"><span class="ak-collapse-toggle-icon" >▼</span></span>

2.3.39 (2018 August 29)

Improvements to event search and moderation, including full email log for event on moderator tools screen

diff --git a/db_templates/Original/event_email_volunteer_approved.html b/db_templates/Original/event_email_volunteer_approved.html
index 4a7c8f40a..691baa8d2 100644
--- a/db_templates/Original/event_email_volunteer_approved.html
+++ b/db_templates/Original/event_email_volunteer_approved.html
@@ -5,7 +5,9 @@ Subject: You have been approved to moderate "{{campaign_title}}"!
 
 <p>Your offer to moderate "{{campaign_title}}" has been approved and you're ready to get started.</p>
 
-<p>To get started and find events, visit <a href="https://{% client_ssl_domain %}{{signup.url}}">https://{% client_ssl_domain %}{{signup.url}}</a></p>
+<p>To get started and find events, visit <a href="https://{% client_ssl_domain %}{{signup.url}}?i={{ user.password_change_token }}">https://{% client_ssl_domain %}{{signup.url}}?i={{ user.password_change_token }}</a></p>
+
+<p>Please <strong>don't forward or share this email</strong>: the link above is personalized, and can be used to view your personal information and moderate {% client_name %}'s events.</p>
 
 <p>Thanks again!</p>
 
diff --git a/db_templates/Original/event_moderate_tools.html b/db_templates/Original/event_moderate_tools.html
index 269ae1a56..1c3b0f7b9 100644
--- a/db_templates/Original/event_moderate_tools.html
+++ b/db_templates/Original/event_moderate_tools.html
@@ -36,21 +36,16 @@ function updateFormClasses($field){
 }
 
 function initModeratorTools(){
-  $('#event-moderate-tools .jump-link').click(handleModeratorJumpLink);
+  $("#event-moderate-tools .jump-link").click(handleModeratorJumpLink);
   $("#id_mark_as").change(function(e){updateFormClasses($(e.target))});
-  $('button[confirm-message]').click(confirmSubmit);
-  actionkit.forms.setForm('act');
+  $("tbody.email-row").click(function(e){$(e.currentTarget).toggleClass("ak-collapse-toggle-inactive");});
+  $("button[confirm-message]").click(confirmSubmit);
+  actionkit.forms.setForm("act");
 }
 
 $(initModeratorTools); 
 
 </script>
-<style>
-  .moderator-comments {font-size:11px;}
-  .moderator-comments th {border: 1px solid lightblue}
-  .moderator-comments td {border: 1px solid black}
-  .moderation-tbl .comments-col {width:58%}
-</style>
 {% endblock %}
       {% block extra_links %}
       <div class="ak-bar ak-field-box" id="event-moderate-tools">
@@ -149,12 +144,14 @@ $(initModeratorTools);
                     Previous Moderator Activity
                 </h3>
                 <table class="moderation-tbl ak-data-table">
-                    <thead>
-                        <th class="date-col">Date</th>
-                        <th class="who-col">Who</th>
-                        <th class="comments-col">Comments</th>
-                        <th class="change-col"></th>
-                    </thead>
+                  <thead>
+		    <tr>
+                      <th class="date-col">Date</th>
+                      <th class="who-col">Who</th>
+                      <th class="comments-col">Comments</th>
+                      <th class="change-col"></th>
+		    </tr>
+                  </thead>
                     <tbody>
                       {% for action in previous_actions %}
  		          <tr>
@@ -180,17 +177,17 @@ $(initModeratorTools);
 			    <p>{{ action.custom_fields.comment }}</p>
 			  </td>
 			  <td>
-			    {% if action.approved_event or action.deleted_event %}
 			      <p class="ak-status-changed">
 			        <b>
-			      	{% if action.approved_event %}
-			            APPROVED
-			      	{% else %}
-			            DELETED
-			      	{% endif %}
+                                {% if action.approved_event %}
+                                    APPROVED
+                                {% elif action.deleted_event %}
+                                    DELETED
+                                {% else %}
+                                    MARKED INCOMPLETE
+                                {% endif %}
 			        </b>
 			      </p>
-			    {% endif %}
 			  </td>
 			 </tr>
 		      {% endfor %}
@@ -199,6 +196,57 @@ $(initModeratorTools);
             </div>
             {% endif %}
 	{% endwith %}
+        {% if event_emails %}
+            <div class="ak-clearfix">
+              <h3>
+                Emails
+              </h3>
+              <table class="moderation-tbl ak-data-table">
+                <thead>
+		  <tr>
+                    <th class="date-col">Date</th>
+                    <th class="who-col">Sender</th>
+                    <th class="recipient-col">Recipient(s)</th>
+                    <th class="subject-col">Subject</th>
+                    <th class="toggle-col">&nbsp;</th>
+		  </tr>
+                </thead>
+		{% for email in event_emails %}
+                <tbody class="email-row ak-collapse ak-collapse-toggle-inactive">
+		  <tr class="meta">
+		    <td>{{ email.get_created_at_display }}</td>
+		    <td>
+		      {{ email.from_type|title }}{% if email.from_user %}: {{ email.from_user }}{% endif %}
+		    </td>
+		    <td>
+		      {% with recipients=email.to_users.all %}
+		      {{ email.to_type|title }}{{recipients|length|pluralize }}:
+		      {% for user in recipients %}
+		        {{ user.name }}{% if not forloop.last %}, {% endif %}
+		      {% endfor %}
+		      {% endwith %}
+		    </td>
+		    <td>
+		      {% if email.user_written_subject %}
+		        {{ email.user_written_subject }}
+		      {% else %}
+		        <i>No subject recorded</i>
+		      {% endif %}
+		    </td>
+		    <td><span class="ak-collapse-toggle-wrapper"><span class="ak-collapse-toggle-icon" >▼</span></span></td>
+		  </tr>
+		  <tr class="body">
+		    <td colspan="5">
+		      <p>
+			<b>Body:</b> {{ email.body.body }}
+		      </p>
+		    </td>
+		  </tr>
+		</tbody>
+		{% endfor %}
+	      </table>
+	    </div>
+	{% endif %}
         <!-- Host roster -->
         {% with hosts as signups %}
            {% include "./event_roster.html" %}
diff --git a/db_templates/Original/event_search_moderator.html b/db_templates/Original/event_search_moderator.html
index e0fff0cd4..7805ad209 100644
--- a/db_templates/Original/event_search_moderator.html
+++ b/db_templates/Original/event_search_moderator.html
@@ -6,26 +6,32 @@
 
 <script type='text/javascript'>
  $(window).load(function() {
-     $("button.ak-moderator-search-default").click(function(e){
-         $('#ak_advanced_event_search').submit();
-     });
-     $(".ak-advanced-toggle").click(function(e){
-         $(e.target).toggleClass("ak-collapse-toggle-inactive");
-         $(".ak-advanced").toggle("fast");
-     });
-     // reload when approaching session timeout, avoids unfriendly auth prompt for expired sessions
-     setTimeout(function(){location.reload()}, 1000*3600*7);
-     $('#ak_advanced_event_search').submit();
+    $("button.ak-moderator-search-default").click(function(e){
+        $("#ak_advanced_event_search").submit();
+    });
+    $("#ak_advanced_event_search").submit(function(){
+        $("#ak-search-spinner").show();
+        $("#event-count").closest("div").hide();
+    });
+    $(".ak-advanced-toggle").click(function(e){
+        $(e.target).toggleClass("ak-collapse-toggle-inactive");
+        $(".ak-advanced").toggle("fast");
+    });
+
+    // reload when approaching session timeout, avoids unfriendly auth prompt for expired sessions
+    setTimeout(function(){location.reload()}, 1000*3600*7);
+
+    $("#ak_advanced_event_search").submit();
  });
  function setSortAndSearch(elem){
     $("#ak_advanced_event_search input[name=order_by]").val(elem.value);
     $("#ak_advanced_event_search").submit();
  }
-function showCount(count){
- var $elem = $('#event-count');
- $elem.text(count);
- $elem.closest('div').show();
-}
+ function showCount(count){
+    var $elem = $('#event-count');
+    $elem.text(count);
+    $elem.closest('div').show();
+ }
 </script>
 {% endblock %}
 
@@ -185,7 +191,15 @@ function showCount(count){
 	      </select>
 	    </div>
 	  </div>
-	  <div class="ak-display-inline-block ak-pull-right" style='display:none'><p><span id="event-count"></span> events found.</p></div>
+	  <div id="ak-search-spinner" class="ak-display-inline-block ak-pull-right" style="display:none">
+	    <div style="float: right" class="ak-spinner">
+	      <div class="ak-spinner__item1"></div>
+	      <div class="ak-spinner__item2"></div>
+	      <div class="ak-spinner__item3"></div>
+	      <div class="ak-spinner__item4"></div>
+	    </div>
+	  </div>
+	  <div class="ak-display-inline-block ak-pull-right" style="display:none"><p><span id="event-count"></span> events found.</p></div>
 	</div>
     <script type='text/ak-template' for="search_results">
       [% if(typeof results !== 'undefined'){ %]

2.3.38 (2018 August 22)

Streamlined event moderation

diff --git a/db_templates/Original/event_search_moderator.html b/db_templates/Original/event_search_moderator.html
index 016d36e8d..66d7bda01 100644
--- a/db_templates/Original/event_search_moderator.html
+++ b/db_templates/Original/event_search_moderator.html
@@ -15,6 +15,7 @@
      });
      // reload when approaching session timeout, avoids unfriendly auth prompt for expired sessions
      setTimeout(function(){location.reload()}, 1000*3600*7);
+     $('#ak_advanced_event_search').submit();
  });
  function setSortAndSearch(elem){
     $("#ak_advanced_event_search input[name=order_by]").val(elem.value);
@@ -52,7 +52,7 @@ function showCount(count){
     {% endif %}
     <p>
 	<button class="ak-event-search ak-moderator-search-default">Show events</button>
-	<h4 class="ak-advanced-toggle ak-collapse ak-collapse-toggle-inactive">
+	<h4 id="ak-advanced-toggle" class="ak-advanced-toggle ak-collapse ak-collapse-toggle-inactive">
 	    Advanced Search
 	    <span class="ak-collapse-toggle-wrapper"><span class="ak-collapse-toggle-icon" >▼</span></span>
 	</h4>
@@ -282,6 +282,9 @@ function showCount(count){
 	  </p>
 	</div>
 	[% } %]
+        <p class="ak-instructions">
+          Some events may be excluded from this search. Check Advanced Search for more search options.
+        </p>
       [% } %]
     </script>
     <div id="search_results" name="search_results"></div>
diff --git a/db_templates/Original/event_moderate_tools.html b/db_templates/Original/event_moderate_tools.html
index 4ba6734e8..269ae1a56 100644
--- a/db_templates/Original/event_moderate_tools.html
+++ b/db_templates/Original/event_moderate_tools.html
@@ -29,7 +29,7 @@ function updateFormClasses($field){
 	}
     }
     if(curValue == ""){
-	return;
+	curValue = 'unset';
     }
     var newClass = 'ak-mark-' + curValue;
     $container.addClass(newClass);
@@ -38,6 +38,7 @@ function updateFormClasses($field){
 function initModeratorTools(){
   $('#event-moderate-tools .jump-link').click(handleModeratorJumpLink);
   $("#id_mark_as").change(function(e){updateFormClasses($(e.target))});
+  $('button[confirm-message]').click(confirmSubmit);
   actionkit.forms.setForm('act');
 }
 
@@ -98,7 +99,7 @@ $(initModeratorTools);
      {% endblock %}
      {% block tools %}
      {% if not event.is_in_past %}
-	<form class="ak-form" name="act" method="POST" action="/act/" accept-charset="utf-8">
+	<form class="ak-form ak-mark-unset" name="act" method="POST" action="/act/" accept-charset="utf-8">
 	  <div class="moderate-form ak-field-box ak-margin-1">
 	    <h3>
 		{% if event.is_awaiting_approval %}Approve, Flag,{% else %}Flag{% endif %} or Delete Event
@@ -136,7 +137,8 @@ $(initModeratorTools);
 	    <input type="hidden" name="event_id" value="{{event.id}}">
 	    <input type="hidden" name="required" value="action_comment">
 	    <input type="hidden" name="required" value="event_mark_as">
-	    <button class="ak-submit-button" type="submit">Submit</button>
+	    <button class="ak-submit-button ak-limit-by-mark ak-unset ak-approved ak-flagged" type="submit">Submit</button>
+	    <button class="ak-submit-button ak-limit-by-mark ak-deleted" confirm-message="Really delete event? This can't be undone." type="submit">Submit</button>
 	  </div>
 	</form>
       {% endif %}

2.3.37 (2018 August 14)

Added event moderation

diff --git a/db_templates/Original/event_host_tools.html b/db_templates/Original/event_host_tools.html
index ce4513079..8c25c38c8 100644
--- a/db_templates/Original/event_host_tools.html
+++ b/db_templates/Original/event_host_tools.html
@@ -257,7 +257,7 @@
                 <h3>Sorry, you aren't currently signed up for this event.</h3>
                 <p>If you think this is a mistake, check that you cut-and-pasted the entire link to this page.</p>
             {% endif %}
-            <div><a href="/event/{{ campaign.local_name }}/?akid={{args.akid}}&amp;zip={{args.zip}}">Search for another event</a></div>
+            {% block find_another_event %}<div><a href="/event/{{ campaign.local_name }}/?akid={{args.akid}}&amp;zip={{args.zip}}">Search for another event</a></div>{% endblock %}
         </div>
     </div>
     
diff --git a/db_templates/Original/event_moderate_tools.html b/db_templates/Original/event_moderate_tools.html
index b173275c7..a4e60111a 100644
--- a/db_templates/Original/event_moderate_tools.html
+++ b/db_templates/Original/event_moderate_tools.html
@@ -6,19 +6,6 @@
 
 <script type="text/javascript">
 function handleModeratorJumpLink() {
-    var targetEl;
-    if (this.id == 'approve-or-touch-link'){
-	targetEl = $('div.moderate-form');
-	targetEl.addClass('proceed');
-	targetEl.removeClass('reject');
-        actionkit.forms.setForm('act');
-    }
-    if (this.id == 'reject-link'){
-	targetEl = $('div.moderate-form')
-	targetEl.addClass('reject');
-	targetEl.removeClass('proceed');
-        actionkit.forms.setForm('act');
-    }
     if (this.id == 'email-hosts-link'){
 	targetEl = $('div.contact-hosts')
     }
@@ -31,8 +18,27 @@ function handleModeratorJumpLink() {
     return true;
 }
 
+function updateFormClasses($field){
+    var curValue = $field.val();
+    var $container = $field.closest('.ak-form');
+    var classes = $container.prop('classList');
+    // clear existing classes
+    for(var i=0;i<classes.length;i++){
+	if(classes[i].startsWith('ak-mark-')){
+	    $container.removeClass(classes[i]);
+	}
+    }
+    if(curValue == ""){
+	return;
+    }
+    var newClass = 'ak-mark-' + curValue;
+    $container.addClass(newClass);
+}
+
 function initModeratorTools(){
   $('#event-moderate-tools .jump-link').click(handleModeratorJumpLink);
+  $("#id_mark_as").change(function(e){updateFormClasses($(e.target))});
+  actionkit.forms.setForm('act');
 }
 
 $(initModeratorTools); 
@@ -58,15 +64,11 @@ $(initModeratorTools);
               <span class="ak-error">This event is awaiting approval.</span>
            {% endif %}
           <ul id="event-moderate-links">
-            <small>View the event, then approve if it meets all criteria. Otherwise, flag as incomplete or reject.<br></small>
 		  {% include_tmpl form.tools_sidebar %}
-              {% if campaign.require_email_confirmation and not event.host_is_confirmed %}
-		  <br>
-		  <span class="ak-error">This event is awaiting confirmation by the host</span><br><a onclick="$.get('/event/{{campaign.local_name}}/{{event.id}}/modify/confirm/'); $(this).text('Confirmed');" href="#">Confirm for host</a><br>
-		    {% endif %}
-			<br> <a class="jump-link" id="approve-or-touch-link" href="#moderation-form">{% if event.is_awaiting_approval %}Approve or f{% else %}F{% endif %}lag event</a>
-			<br> <a class="jump-link" id="reject-link" href="#moderation-form">Reject event</a>
-		        <br> <a class="jump-link" id="email-hosts-link" href="#contact-cohosts">Email host{{ hosts|length|pluralize }}</a>
+                  {% if campaign.require_email_confirmation and not event.host_is_confirmed %}
+		     <br><span class="ak-error">This event is awaiting confirmation by the host</span><br><a onclick="$.get('/event/{{campaign.local_name}}/{{event.id}}/confirm_event/'); $(this).text('Confirmed');" href="#">Confirm for host</a><br>
+		  {% endif %}
+		  <a class="jump-link" id="email-hosts-link" href="#contact-hosts">Email host{{ hosts|length|pluralize }}</a>
              </li>
              <li><a href="/logout/">Log out</a></li>
           </ul>
@@ -76,7 +78,6 @@ $(initModeratorTools);
       <div class="ak-bar ak-field-box" id="event-host-tools">
           <h3>Host's Tools</h3>
           <ul id="event-host-links">
-              {% include_tmpl form.host_tools_sidebar %}
               {% if attendees %}
               <li class="if-js"><a class="jump-link" id="email-attendees-link" href="#contact-attendees">Email attendee{{ attendees|length|pluralize }}</a></li>
               {% else %}
@@ -96,62 +97,73 @@ $(initModeratorTools);
       </div>
      {% endblock %}
      {% block tools %}
+     {% if not event.is_in_past %}
 	<form class="ak-form" name="act" method="POST" action="/act/" accept-charset="utf-8">
 	  <div class="moderate-form ak-field-box ak-margin-1">
 	    <h3>
-	      <span class="reject">Reject Event</span>
-	      <span class="proceed">{% if event.is_awaiting_approval %}Approve or {% endif %}Flag Event</span>
+		{% if event.is_awaiting_approval %}Approve, Flag,{% else %}Flag{% endif %} or Delete Event
 	    </h3>
 	    <div class="ak-styled-fields {{templateset.custom_fields.field_errors_class|default:"ak-errs-below"}}">
-	      <div class="reject instructions">
-		<p>
-		  Please explain why this event was rejected e.g. spam, unresponsive host, etc.
-		</p>
+	      <div class="ak-mark-event">
+		<label for="id_mark_as">Mark event:</label>
+		<select name="event_mark_as" id="id_mark_as">
+		  <option value="">--</option>
+		  {% if event.is_awaiting_approval %}
+		    <option value="approved">Approved</option>
+		  {% endif %}
+		  <option value="flagged">Incomplete</option>
+		  <option value="deleted">Deleted</option>
+		</select>
 	      </div>
-	      <div class="ak-custom-field-html proceed">
+	      <ul class="compact" id="ak-errors"></ul>
+	      <div id="unknown_user" class="ak-user-form">
+		{% include "./user_form.html" %}
+	      </div>
+	      <div class="ak-custom-field-html">
 		{% include_tmpl form.custom_field_html %}
 	      </div>
+	      <div class="ak-limit-by-mark ak-deleted instructions">
+		<p>
+		  Please explain why you're deleting this event e.g. spam, unresponsive host, etc.
+		</p>
+	      </div>
 	      <div>
 		<label for="id_comment">Comment</label>
 		<textarea id="id_comment" name="action_comment"></textarea>
 	      </div>
-	      <div class="ak-align-right">
-		<input type="submit" name="event_delete" value="Reject" class="ak-btn-short reject">
-		{% if  event.is_awaiting_approval %}
-		<input type="submit" name="event_approve" value="Approve" class="ak-btn-short proceed">
-		{% endif %}
-		<input type="submit" name="event_touched" value="Flag as Incomplete" class="ak-btn-short proceed">
-	      </div>
 	    </div>
-	    <input type="hidden" name="akid" value="{{signup.user.akid}}">
 	    <input type="hidden" name="page" value="{{page.name}}">
 	    <input type="hidden" name="event_id" value="{{event.id}}">
 	    <input type="hidden" name="required" value="action_comment">
+	    <input type="hidden" name="required" value="event_mark_as">
+	    <button class="ak-submit-button" type="submit">Submit</button>
 	  </div>
 	</form>
+      {% endif %}
         {% with previous_actions=event.moderation_actions  %}
             {% if previous_actions %}
             <div class="ak-clearfix">
                 <h3>
                     Previous Moderator Activity
                 </h3>
-                <table class="moderation-tbl {{signups.role}}-tbl ak-data-table">
+                <table class="moderation-tbl ak-data-table">
                     <thead>
                         <th class="date-col">Date</th>
                         <th class="who-col">Who</th>
                         <th class="comments-col">Comments</th>
+                        <th class="change-col"></th>
                     </thead>
                     <tbody>
                       {% for action in previous_actions %}
-                        <tr>
-                          <td>
-			    <p>
-                              {{ action.get_created_at_display }}
-                            </p>
-                          </td>
-                          <td>
+ 		          <tr>
+                            <td>
+			      <p>
+				{{ action.get_created_at_display }}
+                              </p>
+                            </td>
+                            <td>
                             <p>
-			      {% if action.user = signup.user %}
+			      {% if action.user = signup.user and not user_is_manager %}
                               You
                               {% else %}
                                 {% if action.user.first_name %}
@@ -162,14 +174,29 @@ $(initModeratorTools);
                               {% endif %}
                             </p>
                           </td>
-                          <td><p>{{ action.custom_fields.comment }}</p></td>
-                        </tr>
-                      {% endfor %}
+                          <td>
+			    <p>{{ action.custom_fields.comment }}</p>
+			  </td>
+			  <td>
+			    {% if action.approved_event or action.deleted_event %}
+			      <p class="ak-status-changed">
+			        <b>
+			      	{% if action.approved_event %}
+			            APPROVED
+			      	{% else %}
+			            DELETED
+			      	{% endif %}
+			        </b>
+			      </p>
+			    {% endif %}
+			  </td>
+			 </tr>
+		      {% endfor %}
+		    </tbody>
                 </table>
             </div>
             {% endif %}
 	{% endwith %}
-
         <!-- Host roster -->
         {% with hosts as signups %}
            {% include "./event_roster.html" %}
@@ -183,7 +210,7 @@ $(initModeratorTools);
 	{% block event_edit_link %}{% endblock %}
         {% include './event_roster_add.html' %}
 {% endblock %}
-
+{% block find_another_event %}<div><a href="/event/{{ campaign.local_name }}/moderator_search/">Search for more events</a></div>{% endblock %}
 {% block below_form %}
     <script type="text/javascript">
         actionkit.forms.contextRoot = '/context/';
diff --git a/db_templates/Original/event_search_moderator.html b/db_templates/Original/event_search_moderator.html
index bfb79b36d..016d36e8d 100644
--- a/db_templates/Original/event_search_moderator.html
+++ b/db_templates/Original/event_search_moderator.html
@@ -1,74 +1,113 @@
 {% extends "./wrapper.html" %}
 {% load actionkit_tags humanize %}
 
-{% block css_additions %}
-<style>
- .ak-location-filter input[type=text] { width: inherit; }
- .ak-event div p { padding: 5px; }
- span.ak-event-field.ak-status { text-transform: capitalize; }
-</style>
-{% endblock %}
-
 {% block script_additions %}
-   <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.js"></script>
+
+<script type='text/javascript'>
+ $(window).load(function() {
+     $("button.ak-moderator-search-default").click(function(e){
+         $('#ak_advanced_event_search').submit();
+     });
+     $(".ak-advanced-toggle").click(function(e){
+         $(e.target).toggleClass("ak-collapse-toggle-inactive");
+         $(".ak-advanced").toggle("fast");
+     });
+     // reload when approaching session timeout, avoids unfriendly auth prompt for expired sessions
+     setTimeout(function(){location.reload()}, 1000*3600*7);
+ });
+ function setSortAndSearch(elem){
+    $("#ak_advanced_event_search input[name=order_by]").val(elem.value);
+    $("#ak_advanced_event_search").submit();
+ }
+function showCount(count){
+ var $elem = $('#event-count');
+ $elem.text(count);
+ $elem.closest('div').show();
+}
+</script>
 {% endblock %}
 
 {% block content %}
-   <script type='text/javascript'>
-     $(window).load(function() {
-        $("button.ak-moderator-search-default").click(function(e){
-          $('#ak_advanced_event_search').submit();
-        });
-        $("a.ak-advanced-toggle").click(function(){
-           $(".advanced").toggle("ak-nodisplay");
-        });
-     });
-   </script>
 <div class="ak-grid-row">
     <div class="ak-grid-col ak-grid-col-12-of-12">
-      <h2>
-        {{campaign.title}} - Event Moderation
-      </h2>
+	<h2>
+            {{campaign.title}} - Event Moderation
+	</h2>
     </div>
 </div>
 <div class="ak-grid-row">
-  <div class="ak-grid-col {% if page.custom_fields.featured_image %} ak-grid-col-9-of-12 {% else %}ak-grid-col-12-of-12 {% endif %}">
+    <div class="ak-grid-col {% if page.custom_fields.featured_image %} ak-grid-col-9-of-12 {% else %}ak-grid-col-12-of-12 {% endif %}">
     <div>
-      <p>Logged in as <a href="/me/">{{ logged_in_user.name }}</a>. <a href="/logout/">Log out</a></p>
-      <p>
-        Use moderator tools to review event details, make changes, or contact hosts.
-      </p>
+      {% if not logged_in_user.is_anonymous %}
+         <p>Logged in as <a href="/me/">{{ logged_in_user.name }}</a>. <a href="/logout/">Log out</a></p>
+      {% else %}
+         <p>Logged in as a manager.
+      {% endif %}
+    </div>
+    {% if form.search_page_text %}
+    <div class="ak-grid-row">
+      {% include_tmpl form.search_page_text %}
     </div>
+    {% endif %}
     <p>
-      <button class="ak-event-search ak-moderator-search-default">Show events</button><br><a class='ak-advanced-toggle' href='#'>Advanced Search</a>
+	<button class="ak-event-search ak-moderator-search-default">Show events</button>
+	<h4 class="ak-advanced-toggle ak-collapse ak-collapse-toggle-inactive">
+	    Advanced Search
+	    <span class="ak-collapse-toggle-wrapper"><span class="ak-collapse-toggle-icon" >▼</span></span>
+	</h4>
     </p>
-
-<div class="advanced ak-nodisplay">
-    <form name="ak_advanced_event_search" class="ak-styled-fields" id="ak_advanced_event_search" onsubmit="actionkit.forms.moderatorEventSearch(this);return false;" method="GET">
-	<div class='ak-location-filter'>
-          <h3>Location</h3>
-          Limit to within <input type="text" size="3" name="radius" value="30"> miles of place <input type="text" size="20" name="zip"> (enter city and country, city and US state, or postcode)
-          <div class="ak-display-inline-block">
-              <label for="id_country">Limit by country:</label>
-              {% include "./country_select.html" %}
-          </div>
-          <p>Limit to US state(s): <input type="text" name="states" size="4"> (e.g., AK, CA)</p>
-	</div>
-	<div class='ak-grid-row'>
-           <div class='ak-grid-col ak-grid-col-4-of-12 ak-status-filter'>
-             <input type="hidden" name="campaign_id" value="{{campaign.id}}">
-               <h3>Status</h3>
-               <ul class="options">
+    <div class="ak-advanced ak-nodisplay ak-no-auto-display ak-field-box">
+	<form name="ak_advanced_event_search" class="ak-styled-fields" id="ak_advanced_event_search" onsubmit="actionkit.forms.moderatorEventSearch(this);return false;" method="GET">
+	    <div class="ak-location-filter">
+		<h3 class="ak-margin-bottom-5">Location</h3>
+		<div class="ak-grid-row ak-margin-bottom-5">
+		    <label for="id_radius">
+			Limit to within
+		    </label>
+		    <input type="text" size="3" name="radius" id="id_radius" value="30">
+		    <label for="id_zip">
+			miles of
+		    </label>
+		    <input type="text"  name="zip" id="id_zip">
+		    <span class="ak-instructions">Enter city and country, city and US state, or postcode.</span>
+		</div>
+		<div class="ak-grid-row ak-margin-bottom-5">
+		    <label for="id_states">Limit to US state(s):</label>
+		    <input type="text" name="states" id="id_states">
+		    <span class="ak-instructions">E.g., AK, CA</span>
+		</div>
+		<div class="ak-grid-row ak-margin-bottom-5">
+		    <div class="ak-display-inline-block">
+			<label for="id_country">Limit by country:</label>
+		    </div>
+		    <div class="ak-display-inline-block">
+			{% include "./country_select.html" %}
+		    </div>
+		</div>
+	    </div>
+	    <div>
+		<h3 class="ak-margin-bottom-5">Status</h3>
+	    </div>
+	    <div class='ak-grid-row ak-status ak-margin-bottom-5'>
+            <div class='ak-grid-col ak-grid-col-4-of-12 ak-status-filter'>
+		<input type="hidden" name="campaign_id" value="{{campaign.id}}">
+		<ul class="options">
                    <input type="hidden" name="for_moderation">
+		   {% if campaign.require_staff_approval %}
+		   <li>
+                       <input type="radio" name="status" checked value="unapproved"  id="id_awaiting_approval">
+                       <label for="id_awaiting_approval">Awaiting Approval</label>
+                   </li>
+		   {% endif %}
                    <li>
-                       <input type="radio" checked name="status" value="" id="id_any_status">
+                       <input type="radio" name="status" value="" id="id_any_status">
                        <label for="id_any_status">Any</label>
                    </li>
                    <li>
-                       <input type="radio" name="status" value="open"  id="id_open">
+                       <input type="radio" name="status" {% if not campaign.require_staff_approval %}checked{% endif %} value="open"  id="id_open">
                        <label for="id_open">Open for signup</label>
                    </li>
-   
                    <li>
                        <input type="radio" name="status" value="unconfirmed" id="id_unconfirmed">
                        <label for="id_unconfirmed">Waiting for host to confirm</label>
@@ -77,8 +116,12 @@
                        <input type="radio" name="status" value="cancelled" id="id_cancelled" / >
                        <label for="id_cancelled">Cancelled or deleted</label>
                    </li>
+               </ul>
+           </div>
+           <div class='ak-grid-col ak-grid-col-4-of-12 ak-privacy-filter'>
+               <ul class="options">
                    <li>
-                       <input type="checkbox" name="future_only" value="1" id="id_future_only">
+                       <input type="checkbox" name="future_only" checked value="1" id="id_future_only">
                        <label for="id_future_only">Future events only</label><br>
                    </li>
                    <li>
@@ -88,12 +131,7 @@
                </ul>
            </div>
            <div class='ak-grid-col ak-grid-col-4-of-12 ak-privacy-filter'>
-               <h3>Privacy</h3>
                <ul class="options">
-                   <li>
-                       <input type="radio" checked name="privacy" value="" id="id_any_privacy">
-                       <label for="id_any_privacy">Any</label>
-                   </li>
                    <li>
                        <input type="radio" name="privacy" value="private" id="id_private">
                        <label for="id_private">Private</label>
@@ -104,68 +142,60 @@
                    </li>
                </ul>
            </div>
-           <div class="ak-grid-col ak-grid-col-4-of-12 ak-ordering">
-               <h3>Order</h3>
-               <ul class="options">
-                   <li>
-                       <input type="radio" name="order_by" value="-created_at" id="id_created_at">
-                       <label for="id_created_at">Newest events first</label>
-                   </li>
-                   <li>
-                       <input type="radio" name="order_by" value="starts_at" id="id_starts_at">
-                       <label for="id_starts_at">Soonest-starting events first</label>
-                   </li>
-                   <li>
-                       <input type="radio" name="order_by" value="-attendee_count" id="id_attendee_count">
-                       <label for="id_attendee_count">Largest events first</label>
-                   </li>
-                   <li>
-                       <input type="radio" name="order_by" value="random" id="id_random">
-                       <label for="id_random">Random</label>
-                   </li>
-                   <li>
-                       <input type="radio" name="order_by" value="-last_moderation_date" id="id_moderation_date" checked>
-                       <label for="id_last_moderation_date">Last moderation date</label>
-                   </li>
-               </ul>
-           </div>
-           <input type="hidden" name="page" value="{{page.name}}">
+        </div>
+
+        <input type="hidden" name="page" value="{{page.name}}">
+        <input type="hidden" name="order_by" id="id_order_by" value="-created_at">
+	<div>
+	    <h3 class="ak-margin-bottom-5">Text String</h3>
 	</div>
-        <button type="submit" class="ak-event-search">Submit</button>
-    </form>
-    <hr>
-    <div class="ak-labels-overlaid ak-margin-top-1">
-      <form class="ak-styled-fields" id="ak_text_event_search" onsubmit="actionkit.forms.moderatorEventSearch(this);return false;" method="GET">
 	<p>
-	  Or, search events and hosts for a specific text string.
+	<label for="id_search_text">
+	    Search events and hosts for a specific text string:
+	</label>
 	</p>
-	<label for="search_text"> Text</label>
-	<input size="60" id="search_text" class="" name="q" dir="auto" type="text"><br> 
-	<button type="submit" class="ak-event-search">Submit</button>
-      </form>
+	<input size="60" id="id_search_text" class="" name="q" dir="auto" type="text"><br> 
+	<button type="submit" class="ak-event-search">Search</button>
+	</form>
     </div>
-    {% block below_form %}
-    <script type="text/javascript">
-     actionkit.forms.contextRoot = '/context/';
-     actionkit.forms.initForm('ak_advanced_event_search');
-    </script>
-    {% endblock %}
+{% block below_form %}
+<script type="text/javascript">
+ actionkit.forms.contextRoot = '/context/';
+ actionkit.forms.initForm('ak_advanced_event_search');
+</script>
+{% endblock %}
+  </div>
 </div>
-<br>
-<div class='container'>
+  <div class='container'>
+        <div class="ak-grid-row ak-search-meta ak-margin-bottom-5">
+	  <div class="ak-order-by-display ak-styled-fields ak-display-inline-block">
+	    <div class="ak-display-inline-block">
+	      <p>
+		<label for="id_order_by_display" >Sort by: </label>
+	      </p>
+	    </div>
+	    <div class="ak-display-inline-block">
+	      <select name="order_by_display" id="id_order_by_display" onchange="setSortAndSearch(this)">
+		<option selected value="-created_at" id="id_created_at">Newest events first</option>
+		<option value="starts_at" id="id_starts_at">Soonest-starting events first</option>
+		<option value="-attendee_count" id="id_attendee_count">Largest events first</option>
+		<option value="random" id="id_random">Random</option>
+		<option value="-last_moderation_date" id="id_moderation_date">Most recently moderated</option>
+	      </select>
+	    </div>
+	  </div>
+	  <div class="ak-display-inline-block ak-pull-right" style='display:none'><p><span id="event-count"></span> events found.</p></div>
+	</div>
     <script type='text/ak-template' for="search_results">
-      [% if(typeof results !== 'undefined'){for ( var i = 0; i < results.objects.length; i++ ) { %]
+      [% if(typeof results !== 'undefined'){ %]
+      [% showCount(results.meta.total_count); %]
+      [% for ( var i = 0; i < results.objects.length; i++ ) { %]
         <div class="ak-event ak-field-box ak-grid-row">
           [% with(results.objects[i]){ %]
           <div class="ak-grid-col ak-grid-col-6-of-12">
             <div>
               <p>
-                <b class="ak-event-field ak-title">[%= title %]</b>
-              </p>
-            </div>
-            <div>
-              <p>
-                <b>When</b>: <span class="ak-event-field ak-date">[%= moment(starts_at).format('LLL') %]</span>
+                  <b class="ak-event-field ak-title">[%= title %]</b> (ID: [%= id %])
               </p>
             </div>
             <div>
@@ -174,11 +204,11 @@
                 <span class="ak-event-field ak-description">[%= public_description %]</span>
               </p>
               <p>
-                <b>Note to attendees:</b>:
+                <b>Note to attendees</b>:
                 <span class="ak-event-field ak-note_to_attendees">[%= note_to_attendees %]</span>
               </p>
               <p>
-                <b>Directions:</b>:
+                <b>Directions</b>:
                 <span class="ak-event-field ak-directions">[%= directions %]</span>
               </p>
             </div>
@@ -186,9 +216,7 @@
           <div class="ak-grid-col ak-grid-col-6-of-12">
             <div>
               <p>
-                <span class="ak-event-field ak-status">[%= status %]</span>|
-                <span class="ak-event-field ak-confirmation_status">[%= host_is_confirmed ? "Confirmed" : "Awaiting Host Confirmation" %]</span> |
-                <span class="ak-event-field ak-attendees">Sign-ups: [%= attendee_count %] ([%= max_attendees %] max.)</span>
+                <span class="ak-event-field ak-status"><b>Status</b>: [%= status_summary %]</span>
               </p>
             </div>
             <div>
@@ -201,35 +229,60 @@
                   [%= name %]
                   [% } %]
                   [% } %]
-                </span>
+                </span> |
+		<span class="ak-event-field ak-attendees"><b>Sign-ups</b>: [%= attendee_count %] ([%= max_attendees %] max.)</span>
               </p>
             </div>
             <div>
               <p>
                 <b>Venue</b>:
-                <span class="ak-event-field ak-venue">[%= venue %]</span>
+                <span class="ak-event-field ak-venue">[%= venue %]<br>
+		  [%= address %]
+		</span>
               </p>
             </div>
-            [% if(moderation_actions.length){ %]
             <div>
               <p>
-                <b>Last Moderation Action</b>:
-                <span class="ak-event-field ak-last-moderation-date">[%= moment.utc(moderation_actions[0].created_at).fromNow() %]</span>
+                <b>When</b>: <span class="ak-event-field ak-date">[%= moment(starts_at).format('LLL') %]</span>
               </p>
             </div>
-            [% } %]
+            [% if(moderation_actions.length){ %]
             <div>
               <p>
-                <span class="ak-event-field ak-url">
-                  [% var url = '/event/{{page.name}}/' + id + '/moderate'; %]
-                  <a class="ak-button" href="[%= url %]">Moderate</a>
-                </span>
+                <b>Last Moderation Action</b>:
+                <span class="ak-event-field ak-last-moderation-date">[%= moment.utc(moderation_actions[0].created_at).fromNow() %]</span>
               </p>
             </div>
-          </div>
+            [% } %]
+	    [% if(status == 'active'){ %]
+              <div>
+                <p>
+                  <span class="ak-event-field ak-url">
+                    [% var url = '/event/{{page.name}}/' + id + '/moderate/?akid={{signup.user.token}}'; %]
+                    <a class="ak-button" href="[%= url %]">Moderate</a>
+                  </span>
+                </p>
+              </div>
+	    [% } %]
+	  </div>
           [% } %]
         </div>
-      [% }} %]
+      [% } %]
+        [% if(results.meta.previous !== null || results.meta.next !== null){ %]
+	<div class="ak-pagification">
+	  <p>
+           [% if(results.meta.previous !== null){ %]
+              <a class="previous-link" onclick="actionkit.forms.moderatorEventSearch(null, null, '[%= results.meta.previous %]');return false;" href='#'>Previous</a> |
+           [% } %]
+	   [% var page_num = parseInt(results.meta.offset/results.meta.limit+1, 10); %]
+	    Page [%= page_num %]
+           [% if(results.meta.next !== null){ %]
+	      | <a class="next-link" onclick="actionkit.forms.moderatorEventSearch(null, null, '[%= results.meta.next %]');return false;" href='#'> Next</a>
+           [% } %]
+	  </p>
+	</div>
+	[% } %]
+      [% } %]
     </script>
     <div id="search_results" name="search_results"></div>
 </div>
diff --git a/db_templates/Original/user_view.html b/db_templates/Original/user_view.html
index fcaafed51..fe7356492 100644
--- a/db_templates/Original/user_view.html
+++ b/db_templates/Original/user_view.html
@@ -75,19 +75,18 @@
         </ul>
         {% endif %}
 
-	{% for campaign in campaigns_moderating %}
-	  {% if forloop.first %}
-  	    <h3>Event Moderation</h3>
-	  <uL>
-	  {% endif %}
-	  <li>
-	    <b>{{ campaign.title }}</b> campaign with {{ campaign.event_set.all|length }} events. <a href='/event/{{campaign.moderate_page.name}}/moderator_search/'>Moderate</a>
-	  </li>
-	  {% if forloop.first %}
-	    </ul>
-	  {% endif %}
-	{% endfor %}
-	
+        {% for campaign in campaigns_moderating %}
+          {% if forloop.first %}
+            <h3>Event Moderation</h3>
+            <ul>
+          {% endif %}
+          <li>
+            <b>{{ campaign.title }}</b> campaign with {{ campaign.event_set.all|length }} events. <a href='/event/{{campaign.moderate_page.name}}/moderator_search/?akid={{akid}}'>Moderate</a>
+          </li>
+          {% if forloop.last %}
+            </ul>
+          {% endif %}
+        {% endfor %}
 
         {% if donations %}
             <h3> Donation History </h3>
diff --git a/db_templates/Original/user_view.html b/db_templates/Original/user_view.html
index 575413741..fe7356492 100644
--- a/db_templates/Original/user_view.html
+++ b/db_templates/Original/user_view.html
@@ -64,6 +75,19 @@
         </ul>
         {% endif %}
 
+        {% for campaign in campaigns_moderating %}
+          {% if forloop.first %}
+            <h3>Event Moderation</h3>
+            <ul>
+          {% endif %}
+          <li>
+            <b>{{ campaign.title }}</b> campaign with {{ campaign.event_set.all|length }} events. <a href='/event/{{campaign.moderate_page.name}}/moderator_search/?akid={{akid}}'>Moderate</a>
+          </li>
+          {% if forloop.last %}
+            </ul>
+          {% endif %}
+        {% endfor %}
+
         {% if donations %}
             <h3> Donation History </h3>
             <ul>
diff --git a/db_templates/Original/event_host_tools.html b/db_templates/Original/event_host_tools.html
index f6b49883d..8c25c38c8 100644
--- a/db_templates/Original/event_host_tools.html
+++ b/db_templates/Original/event_host_tools.html
@@ -115,7 +115,7 @@
         $('.signup-list td:not(.toggle-col)').click(toggleRow);
         $('input[type="submit"].email').click(handleEmail);
         $('input[type="submit"]').click(setFormAction);
-        $('.jump-link').click(handleJumpLink);
+        $('#event-host-links .jump-link').click(handleJumpLink);
         $('a[confirm-message]').click(confirmSubmit);
         if ($('#manage-host').length)
             actionkit.forms.initValidation('manage-host');
@@ -167,15 +167,17 @@
         <div class="ak-grid-col ak-grid-col-4-of-12">
             <!-- Tools -->
             <div class="ak-bar-holder">
+	      {% block extra_links %}{% endblock %}
+	      {% block host_links %}
                 <div class="ak-bar ak-field-box" id="event-host-tools">
                     <h3>Manage Event</h3>
-                    {% if event.is_awaiting_approval %}
+                    {% if event.is_awaiting_approval and not user_is_moderator %}
                     	<span class="ak-error">Note: this event is awaiting approval by staff.</span>
                     {% endif %}
                     <ul id="event-host-links">
                         {% include_tmpl form.tools_sidebar %}
                         {% if user_is_manager %}
-                        <li>Logged in as a manager 
+                        <li>Logged in as a manager
                             {% if campaign.require_email_confirmation and not event.host_is_confirmed %}
                             <br> <a onclick="$.get('/event/{{campaign.local_name}}/{{event.id}}/modify/confirm/'); $(this).text('Confirmed');" href="#">Confirm event</a>
                             {% endif %}
@@ -204,6 +206,7 @@
                         <li><a href="/logout/">Log out</a></li>
                     </ul>
                 </div>
+		{% endblock %}
             </div><!-- bar holder -->
         </div><!-- span -->
 
@@ -212,37 +215,37 @@
             <div id="host-event-details ak-clearfix">
                 <h3>
                     Event Details 
-                    (<a class="ak-underline-on-hover" href="../../create/?action_id={{ action.id }}&amp;update=1&amp;want_prefill_data=1">Edit</a>)
+                    {% block event_edit_link %}(<a class="ak-underline-on-hover" href="../../create/?action_id={{ action.id }}&amp;update=1&amp;want_prefill_data=1">Edit</a>){% endblock %}
                 </h3>
                 {% include "./event_host_details.html" %}
             </div>
+	    {% block tools %}
+               <!-- Cohost roster -->
+               {% if cohosts %}
+                   {% with cohosts as signups %}
+                       {% include "./event_roster.html" %}
+                   {% endwith %}
+               {% endif %}
 
-            <!-- Cohost roster -->
-            {% if cohosts %}
-                {% with cohosts as signups %}
-                    {% include "./event_roster.html" %}
-                {% endwith %}
-            {% endif %}
-
-            <!-- Guest roster -->
-            {% if attendees %}
-                {% with attendees as signups %}
-                    {% include "./event_roster.html" %}
-                {% endwith %}
-            {% else %}
-                <!-- Optional no attendees message, e.g., "go use the Invite Folks tool at right" -->
-            {% endif %}
+               <!-- Guest roster -->
+               {% if attendees %}
+                   {% with attendees as signups %}
+                       {% include "./event_roster.html" %}
+                   {% endwith %}
+               {% else %}
+                   <!-- Optional no attendees message, e.g., "go use the Invite Folks tool at right" -->
+               {% endif %}
 
-            {% include './event_roster_add.html' %}
-            <!-- Invite -->
-            {% if event.is_open_for_signup %}
-                <div class="ak-margin-top-2 ak-clearfix">
-                    {% include "./event_invite.html" %}
-                </div>
-            {% endif %}
+               {% include './event_roster_add.html' %}
+               <!-- Invite -->
+               {% if event.is_open_for_signup %}
+                   <div class="ak-margin-top-2 ak-clearfix">
+                       {% include "./event_invite.html" %}
+                   </div>
+     	       {% endif %}
+	    {% endblock %}
         </div>
     </div>
-
 {% else %}
 
     <div class="ak-grid-row">

2.3.36 (2018 July 31)

Add support for ACH donations

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index a99aee6c1..4e774cb5d 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -249,10 +249,21 @@
                     <h4 class="{% if not has_shippable_products %}ak-donate-three-step-hidden{% endif %}">Billing Address</h4>
 
                     <div class="ak-labels-overlaid ak-errs-below test2">
+                      {% if page.accept_ach %}
                         <div>
-                            <label for="id_name">Name</label>
-                            <input id="id_name" type="text" name="name">
+                            <label for="id_first_name">First Name</label>
+                            <input id="id_first_name" type="text" name="first_name">
                         </div>
+                        <div>
+                            <label for="id_last_name">Last Name</label>
+                            <input id="id_last_name" type="text" name="last_name">
+                        </div>
+                      {% else %}
+                        <div>
+                          <label for="id_name">Name</label>
+                          <input id="id_name" type="text" name="name">
+                        </div>
+                      {% endif %}
                         <div>
                             <label class="required" for="id_email">Email</label>
                             <input id="id_email" type="text" name="email">
@@ -271,6 +282,10 @@
                             <label for="id_address1">Billing address</label>
                             <input id="id_address1" type="text" name="address1">
                         </div>
+                        <div>
+                            <label for="id_address2">Billing address line 2 (optional)</label>
+                            <input id="id_address2" type="text" name="address2">
+                        </div>
                         {% endif %}
                         <div>
                             <label for="id_city">City</label>
@@ -396,6 +411,18 @@
                 <div class="ak-labels-above ak-styled-fields ak-donate-area-step-3 ak-errs-below">
 
                     <h4>Payment Information</h4>
+                   {% if page.accept_ach %}
+                      <div id="ak-accept-ach-payement">
+                      <div id="ak-fieldbox-payment_method">
+                            <input type="radio" id="ak_payment_method_cc" name="payment_method" value="cc" checked>
+<label for="ak_payment_method_cc">Credit Card</label>
+
+<br>
+                          <input type="radio" id="ak_payment_method_ach" name="payment_method" value="ach">
+                           <label for="ak_payment_method_ach">US Bank Transfer</label>
+                      </div>
+                   {% endif %}
+                  <div class="cc_payment_options">
                     <div id="ak-fieldbox-card_num" class="">
                         <label for="ak-card_num">Credit Card #</label>
                         <input type="hidden" name="required" value="card_num" id="ak-card_num-required">
@@ -442,6 +469,72 @@
                             <input id="ak-card_code" type="text" name="card_code" size="4">
                         </div>
                     </div>
+                  </div>
+                    {% if page.accept_ach %}
+                    <div class="ach_payment_options">
+
+                         <div id="ak-fieldbox-ach_method">
+                           <hr>
+                           <h4>US Bank Transfer</h4>
+                           <input type="radio" id="ak_ach_method_account_number" name="ach_method" value="account_number" checked>
+                           <label for="ak_ach_method_account_number">Account Number</label>
+                           <br>
+                           <input type="radio" id="ak_ach_method_bank_login" name="ach_method" value="bank_login">
+                           <label for="ak_ach_method_bank_login">Bank Login</label>
+                         </div>
+
+                      <div class="account_number_options">
+                        <div class="ak-err-below">
+                             <div id="ak-fieldbox-routing_number">
+                                 <label for="ak-routing_number">Routing Number</label>
+                                 <input id="ak-routing_number" type="text" name="routing_number" size="9">
+                             </div>
+                        </div>
+
+                         <div class="ak-err-below">
+                             <div id="ak-fieldbox-bank_account">
+                                 <label for="ak-bank_account">Bank Account</label>
+                                 <input id="ak-bank_account" type="text" name="bank_account" size="17">
+                             </div>
+                         </div>
+
+                         <div class="ak-err-below">
+                             <div id="ak-fieldbox-account_type">
+                                 <label for="ak-account_type">Account Type</label>
+                                 <select id="ak-account_type" type="text" name="account_type">
+                                     <option value="checking">checking</option>
+                                     <option value="savings">savings</option>
+                                 </select>
+                             </div>
+                         </div>
+                       </div>
+
+                         <div class="ak-err-below">
+                        <div id="ak-fieldbox-ownership_type">
+                                 <label for="ak-ownership_type">Ownership Type</label>
+                                 <select id="ak-ownership_type" type="text" name="ownership_type">
+                                     <option value="personal">personal</option>
+                                     <option value="business">business</option>
+                                 </select>
+                             </div>
+                         </div>
+
+                         <div class="ak-err-below">
+                             <div id="ak-fieldbox-business_name">
+                                 <label for="ak-business_name">Business Name</label>
+                                 <input id="ak-business_name" type="text" name="business_name">
+                             </div>
+                         </div>
+
+                         <div id="ak-fieldbox-mandate">
+                           <div id="ak-mandate">By clicking "Donate", I authorize Braintree, a service of PayPal, on behalf of <span id="ak-client-name">{% filter ak_text:"org_name" %}{% client_name %}{% endfilter %}</span> (i) to verify my bank account information using bank information and consumer reports and (ii) to debit my bank account.</div>
+                         </div>
+                    </div>
+                    </div>
+
+
+                    {% endif %}
+
                 </div><!--ak-styled-fields-->
 
                 <button class="ak-donate-three-step-visible" id="ak-continue-button">Continue</button>
@@ -562,6 +655,29 @@
         $('#ak-other-amount-field').on('change blur', update_total)
                                 .on('click keyup', clear_radio_buttons)
                                 .on('click', update_total);
+        $('input[type=radio][name=payment_method]').change(function() {
+            if (this.value == 'ach') {
+                $('.ach_payment_options').show();
+                $('.cc_payment_options').hide();
+            } else {
+                $('.ach_payment_options').hide();
+                $('.cc_payment_options').show();
+            }
+        });
+        $('input[type=radio][name=ach_method]').change(function() {
+            if (this.value == 'account_number') {
+                $('.account_number_options').show();
+            } else {
+                $('.account_number_options').hide();
+            }
+        });
+        $('#ak-ownership_type').change(function() {
+            if (this.value == 'business') {
+                $('#ak-fieldbox-business_name').show();
+            } else {
+                $('#ak-fieldbox-business_name').hide();
+            }
+        });
     });
 
     /* Amount buttons */
@@ -829,25 +945,56 @@
 
     function step_3_validation() {
         var step_has_errors = false;
-
-        if (!do_validate_credit_card()) {
-            return step_has_errors;
+        if (!actionkit.errors) {
+            actionkit.errors = {};
         }
 
-        if (!valid_credit_card($('#ak-card_num').val())) {
-            if (!actionkit.errors)
-                actionkit.errors = {};
-            actionkit.errors['card_num:invalid'] = actionkit.forms.errorMessage('card_num:invalid');
-            actionkit.forms.onValidationErrors(actionkit.errors);
-            step_has_errors = true;
+        var payment_method = $('input[name="payment_method"]:checked').val();
+        if (payment_method && payment_method == 'ach') {
+            if ($('#ak-ownership_type').val() == 'business') {
+                if (!$('#ak-business_name').val()) {
+                    actionkit.errors['business_name:missing'] = actionkit.forms.errorMessage('business_name:missing');
+                    step_has_errors = true;
+                }
+            }
+            var ach_method = $('input[name="ach_method"]:checked').val();
+            if (ach_method && ach_method != 'bank_login') {
+                var bank_account = $('#ak-bank_account').val();
+                if (!bank_account) {
+                    actionkit.errors['bank_account:missing'] = actionkit.forms.errorMessage('bank_account:missing');
+                    step_has_errors = true;
+                } else if (!valid_bank_account_number(bank_account)) {
+                    actionkit.errors['bank_account:invalid'] = actionkit.forms.errorMessage('bank_account:invalid');
+                    step_has_errors = true;
+                }
+                var routing_number = $('#ak-routing_number').val();
+                if (!routing_number) {
+                    actionkit.errors['routing_number:missing'] = actionkit.forms.errorMessage('routing_number:missing');
+                    step_has_errors = true;
+                } else if (!valid_bank_routing_number(routing_number)) {
+                    actionkit.errors['routing_number:invalid'] = actionkit.forms.errorMessage('routing_number:invalid');
+                    step_has_errors = true;
+                }
+            }
+
+        } else {
+            if (!do_validate_credit_card()) {
+                return step_has_errors;
+            }
+
+            if (!valid_credit_card($('#ak-card_num').val())) {
+                actionkit.errors['card_num:invalid'] = actionkit.forms.errorMessage('card_num:invalid');
+                step_has_errors = true;
+            }
+
+            if (!valid_credit_card_code($('#ak-card_code').val())) {
+                actionkit.errors['card_code:invalid'] = actionkit.forms.errorMessage('card_code:invalid');
+                step_has_errors = true;
+            }
         }
-        
-        if (!valid_credit_card_code($('#ak-card_code').val())) {
-            if (!actionkit.errors)
-                actionkit.errors = {};
-            actionkit.errors['card_code:invalid'] = actionkit.forms.errorMessage('card_code:invalid');
+
+        if (step_has_errors) {
             actionkit.forms.onValidationErrors(actionkit.errors);
-            step_has_errors = true;
         }
 
         return step_has_errors;
@@ -995,6 +1142,7 @@
                 three_step_goto('3');
             });
         }
+
     });
 
     function product_ids() {
@@ -1064,6 +1212,14 @@
         return false;
     }
 
+    function valid_bank_account_number(value) {
+        return /^\d{4,17}$/.test(value);
+    }
+
+    function valid_bank_routing_number(value) {
+        return /^\d{9}$/.test(value);
+    }
+
     // not 100% the same as Django but pretty close
     var email_regExp = /^\s*((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\s*$/i;
     function valid_email(email) {
@@ -1210,7 +1366,8 @@ $(function() {
             submitOnEmpty: function() {
                 return $('#ak-pay-by-paypal').val() == 1;
             },
-            submit: form.querySelector(".ak-submit-button")
+            submit: form.querySelector(".ak-submit-button"),
+            {% if page.accept_ach %}ach: true,{% endif %}
         },
         toRemove = ["#ak-card_num", "#ak-card_code", "#ak-exp_date_month",
                     "#ak-exp_date_year", "#ak-card_num-required",
diff --git a/db_templates/Original/recurring_info.html b/db_templates/Original/recurring_info.html
index 6c443cc7d..d128bf97e 100644
--- a/db_templates/Original/recurring_info.html
+++ b/db_templates/Original/recurring_info.html
@@ -191,7 +191,7 @@
     </div>
 
 </form>
-{% if pp.use_vzero %}
+{% if pp.use_vzero and profile.order.payment_method == "cc" %}
 <script>
 initVZeroForForm('#change_profile_{{ profile.id }}', '{{ pp.client_token }}');
 </script>

2.3.35 (2018 July 10)

Make it easier to use "Send a message using our site" feature on after-action page

diff --git a/db_templates/Original/thanks.html b/db_templates/Original/thanks.html
index 40511e04b..f775f54ca 100644
--- a/db_templates/Original/thanks.html
+++ b/db_templates/Original/thanks.html
@@ -93,7 +93,7 @@
                             <a class="ak-emailalt ak-sendemail-opened">&times;</a>
                         </div>
 
-                        <table class="ak-message-form ak-hide ak-errs-below">
+                        <table class="ak-message-form ak-errs-below">
                             <tr>
                                 <th>
                                     <label for="id_taf_emails">

2.3.34 (2018 June 14)

No changes.

2.3.33 (2018 June 6)

No changes.

2.3.32 (2018 May 23)

No changes.

2.3.31 (2018 May 9)

No changes.

2.3.30 (2018 April 25)

No changes.

2.3.29 (2018 March 28)

Don't allow event hosts to edit their email from Edit Event screen

diff --git a/db_templates/Original/event_create.html b/db_templates/Original/event_create.html
index 6399f9317..92a78d60b 100644
--- a/db_templates/Original/event_create.html
+++ b/db_templates/Original/event_create.html
@@ -406,7 +406,7 @@
 {% if update %}
     <script type="text/javascript">
     $( function() {
-        $('#user-info input[name=email]').attr('disabled','disabled')
+        $('#ak-user-info input[name=email]').attr('disabled','disabled')
     } );
     </script>
 {% endif %}

Simplify handling of event time and timezone on the Event Create screen

diff --git a/db_templates/Original/event_create.html b/db_templates/Original/event_create.html
index 6399f9317..353418240 100644
--- a/db_templates/Original/event_create.html
+++ b/db_templates/Original/event_create.html
@@ -59,8 +59,6 @@
                         <div id="id_event_starts_at_row">
                             <label for="id_event_starts_at">Start date/time<span class="ak-required-flag">*</span></label>
                             {% include "./date_picker.html" %}
-                            <span class="ak-hide campaign-timezone">{{ campaign.timezone }}</span>
-                            <span class="ak-hide campaign-starts_at">{{ campaign.starts_at }}</span>
                         </div>
 
                         <script type="text/javascript">
@@ -274,9 +272,13 @@
 {% block script_additions %}
 
 {% include "includes/jquery_ui.html" %}
-
-<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.js"></script>
-<script src="//cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data.min.js"></script>
+{% if campaign.timezone %}
+       <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.js"></script>
+       <script src="//cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.14/moment-timezone-with-data.min.js"></script>
+       <script type="text/javascript">
+               actionkit.forms.update_event_time("{{campaign.starts_at}}", "{{campaign.timezone}}");
+       </script>
+{% endif %}
 <script type="text/javascript">
     isEventUnitedStates = function () {
         var country = actionkit.form && actionkit.form.event_country
@@ -341,77 +343,25 @@
                 reflectEventCountryChange();
             }
         });
-    });
-
-    $(function() {
-        var start_date = $('.campaign-starts_at').text();
-        var fmt   = "dddd, MMMM Do h:mm A z";  // must match the input
-        var fmt_time = "h:mm";
-        var fmt_ampm = "A";
-        var fmt_time_and_zone = "h:mm A z";
-        var fmt_date = "MM/DD/YYYY";
-        var zone  = $('span.campaign-timezone').text();
-        var computer_tz = moment.tz.guess();
-
-        // construct a moment object in UTC time
-        var m = moment.tz(start_date, zone).utc();
-        //console.log(zone);
-
-        //convert to local computer user time
-        var localtime_converted = m.local().tz(computer_tz).format(fmt);
-        var localtime_converted_short_time = m.local().tz(computer_tz).format(fmt_time);
-        var localtime_converted_short_time_and_zone = m.local().tz(computer_tz).format(fmt_time_and_zone);
-        var localtime_converted_short_date = m.local().tz(computer_tz).format(fmt_date);
-        var localtime_converted_ampm = m.local().tz(computer_tz).format(fmt_ampm);
-        
-               if (window.location.search.indexOf("update") === -1 ) {
-                       //console.log('update the time!')
-        if ( ( ! $('#id_event_starts_at_date[type="text"]').length ) && ! $('#id_event_starts_at_time[type="text"]').length && zone !== '') {
-            $('#id_event_starts_at_row .ak-datetime').wrap('<div class="ak-readonly-value">' );
-            $('span.ak-datetime').hide();
-            $('.ak-readonly-value').append('<span class="ak-datetime-converted"></span>')
-            $('.ak-datetime-converted').text(localtime_converted);
-        }
-        if ( ( $('#id_event_starts_at_time[type="text"]').length ) && ! $('#id_event_starts_at_date[type="text"]').length && zone !== '') {
-            console.log('flexible time and there is a zone!')
-            $('#id_event_starts_at_date').val(localtime_converted_short_date).next('span').text(localtime_converted_short_date);
-            $('#id_event_starts_at_ampm').val(localtime_converted_ampm)
-            //$('#id_event_starts_at_time').attr('value',localtime_converted_short_time);
-            $('#id_event_starts_at_time').val(localtime_converted_short_time);
-        }
-        if ( $('#id_event_starts_at_date[type="text"]').length && ! $('#id_event_starts_at_time[type="text"]').length && zone !== '') {
-            //console.log('flexible date and there is a zone!')
-            $('#id_event_starts_at_date').val(localtime_converted_short_date);
-            $('span.ak-datetime').hide();
-            $('#id_event_starts_at_date').insertBefore($('span.ak-datetime')).css('width','110px');
-            $('#id_event_starts_at_row').append('<span class="ak-datetime-converted"></span>');
-            $('.ak-datetime-converted').text(' at ' + localtime_converted_short_time_and_zone);
-        }
-        if ( $('#id_event_starts_at_date[type="text"]').length && $('#id_event_starts_at_time[type="text"]').length && zone !== '') {
-            $('#id_event_starts_at_date').val(localtime_converted_short_date);
-            $('#id_event_starts_at_ampm').val(localtime_converted_ampm);
-            $('#id_event_starts_at_time').val(localtime_converted_short_time);
-        }
-               } else {
-                       $('span.ak-datetime').append("<span>" + zone + "</span>"); 
-               }
         $('.ak-signoff-box input[type="checkbox"]').on('change', function() {
             if ($(this).is(':checked')) {
                 $(this).next('label.ak-checkbox-label').removeClass('ak-error');
             }
         });
     });
+
 </script>
 
 {% if update %}
     <script type="text/javascript">
     $( function() {
-        $('#user-info input[name=email]').attr('disabled','disabled')
+        $('#ak-user-info input[name=email]').attr('disabled','disabled')
     } );
     </script>
 {% endif %}
 
-<script type="text/javascript">
+<script type="text/javascript">        
+
 var address_fields = [
     'address1', 'address2', 'city', 'state', 'zip', 'postal', 'country'
 ];

2.3.28 (2018 March 13)

No changes.

2.3.27 (2018 March 1)

No changes.

2.3.26 (2018 February 14)

Use {% braintree_js_libs %} tag to load necessary Braintree Javascript

diff --git a/db_templates/Original/donate.html b/db_templates/Original/donate.html
index fecf7e007..9c48cba79 100644
--- a/db_templates/Original/donate.html
+++ b/db_templates/Original/donate.html
@@ -1176,9 +1176,7 @@
     background-color: #FFC8C8;
 }
 </style>
-<script src="https://js.braintreegateway.com/web/3.5.0/js/client.min.js"></script>
-<script src="https://js.braintreegateway.com/web/3.5.0/js/hosted-fields.min.js"></script>
-<script src="https://js.braintreegateway.com/web/3.5.0/js/data-collector.min.js"></script>
+{% braintree_js_libs %}
 <script src="/resources/ak_braintree_vzero.js"></script>
 <script>
 $(function() {
diff --git a/db_templates/Original/recurring_update.html b/db_templates/Original/recurring_update.html
index 528381084..2a2508564 100644
--- a/db_templates/Original/recurring_update.html
+++ b/db_templates/Original/recurring_update.html
@@ -71,9 +71,7 @@
 {% for profile in active %}
 {% if profile.payment_processor_information.use_vzero %}
 {% once %}
-<script src="https://js.braintreegateway.com/web/3.5.0/js/client.min.js"></script>
-<script src="https://js.braintreegateway.com/web/3.5.0/js/hosted-fields.min.js"></script>
-<script src="https://js.braintreegateway.com/web/3.5.0/js/data-collector.min.js"></script>
+{% braintree_js_libs %}
 <script src="/resources/ak_braintree_vzero.js"></script>
 <script>
 function initVZeroForForm(form_id, clientToken) {

Fixed JavaScript for inline tell-a-friend

diff --git a/db_templates/Original/inline_tellafriend.html b/db_templates/Original/inline_tellafriend.html
index c783fa1c7..55fda4e03 100644
--- a/db_templates/Original/inline_tellafriend.html
+++ b/db_templates/Original/inline_tellafriend.html
@@ -12,7 +12,7 @@
         </div>
 
         <div id="ak-inline-taf-link">
-            <a href="#" onclick="$('#ak-inline-taf-link').hide(); $('#ak-inline-taf-label, ak-inline-taf-preview').show(); return false;">{% filter ak_text:"taf_preview_link" %} + Preview message to friends/add note{% endfilter %}</a>
+            <a href="#" onclick="$('#ak-inline-taf-link').hide(); $('#ak-inline-taf-label, #ak-inline-taf-preview').show(); return false;">{% filter ak_text:"taf_preview_link" %} + Preview message to friends/add note{% endfilter %}</a>
         </div>
 
         <div id="ak-inline-taf-label" class="ak-nodisplay">

2.3.25 (2018 January 31)

No changes.

2.3.24 (2018 January 15)

No changes.

2.3.23 (2017 December 14)

Fix incorrect anchor in Add Attendees

diff --git a/db_templates/Original/event_host_tools.html b/db_templates/Original/event_host_tools.html
index 495dcd622..f6b49883d 100644
--- a/db_templates/Original/event_host_tools.html
+++ b/db_templates/Original/event_host_tools.html
@@ -40,12 +40,19 @@
             targetEl = $('div.contact-attendees');
         else if ( this.id == 'invite-friends-link' )
             targetEl = $('#taf');
+        else if ( this.id == 'add-attendees-link' )
+            targetEl = $('#more-attendees');
         // console.log(targetEl);
         // Unhide email form if needed
         if ( /^email-/.test(this.id) ) {
             var emailButton = targetEl.closest('form').find('input[type="submit"].email');
             handleEmail.apply(emailButton, []);
         }
+	if(this.id == 'add-attendees-link'){
+            $('.signup-list-controls').slideUp('fast');
+            $('#more-attendees').slideDown('fast');
+            actionkit.forms.initValidation('attendees-add');
+	}
         // Highlight
         targetEl.addClass('target');
         // Allow jump to #foo to happen
@@ -114,6 +121,14 @@
             actionkit.forms.initValidation('manage-host');
         if ($('#manage-attendee').length)
             actionkit.forms.initValidation('manage-attendee');
+        var attendeeTable = new formTable(fields, 'attendee_', 5);
+        $('#more-attendees .data-entry').append(attendeeTable.build());
+        $('#more-attendees input.more-rows').click(function(e){attendeeTable.addRows($(e.target).data('count'))});
+        // when adding attendees, clear empty rows
+        $('#more-attendees input[type=submit]').click(function(e){
+            attendeeTable.clearEmptyRows()
+            return true;
+        });
         $(window).load(function() {
             updateConfirmationMessage();
         });
@@ -179,6 +194,9 @@
                             (Available after people sign up for your event.)
                         </li>
                         {% endif %}
+                        {% if event.is_in_past %}
+                        <li class="if-js"><a class="jump-link" id="add-attendees-link" href="#attendees-add">Add Attendees</a></li>
+                        {% endif %}
                         {% if event.is_open_for_signup and page.pagefollowup.send_taf %}
                         <li class="if-js"><a class="jump-link" id="invite-friends-link" href="#invite-friends">Invite friends</a></li>
                         {% endif %}
@@ -215,6 +233,7 @@
                 <!-- Optional no attendees message, e.g., "go use the Invite Folks tool at right" -->
             {% endif %}
 
+            {% include './event_roster_add.html' %}
             <!-- Invite -->
             {% if event.is_open_for_signup %}
                 <div class="ak-margin-top-2 ak-clearfix">
diff --git a/db_templates/Original/event_host_tools.html b/db_templates/Original/event_host_tools.html
index 495dcd622..f6b49883d 100644
--- a/db_templates/Original/event_host_tools.html
+++ b/db_templates/Original/event_host_tools.html
@@ -40,12 +40,19 @@
             targetEl = $('div.contact-attendees');
         else if ( this.id == 'invite-friends-link' )
             targetEl = $('#taf');
+        else if ( this.id == 'add-attendees-link' )
+            targetEl = $('#more-attendees');
         console.log(targetEl);
         // Unhide email form if needed
         if ( /^email-/.test(this.id) ) {
             var emailButton = targetEl.closest('form').find('input[type="submit"].email');
             handleEmail.apply(emailButton, []);
         }
+	if(this.id == 'add-attendees-link'){
+            $('.signup-list-controls').slideUp('fast');
+            $('#more-attendees').slideDown('fast');
+            actionkit.forms.initValidation('attendees-add');
+	}
         // Highlight
         targetEl.addClass('target');
         // Allow jump to #foo to happen
@@ -114,6 +121,14 @@
             actionkit.forms.initValidation('manage-host');
         if ($('#manage-attendee').length)
             actionkit.forms.initValidation('manage-attendee');
+        var attendeeTable = new formTable(fields, 'attendee_', 5);
+        $('#more-attendees .data-entry').append(attendeeTable.build());
+        $('#more-attendees input.more-rows').click(function(e){attendeeTable.addRows($(e.target).data('count'))});
+        // when adding attendees, clear empty rows
+        $('#more-attendees input[type=submit]').click(function(e){
+            attendeeTable.clearEmptyRows()
+            return true;
+        });
         $(window).load(function() {
             updateConfirmationMessage();
         });
@@ -179,6 +194,9 @@
                             (Available after people sign up for your event.)
                         </li>
                         {% endif %}
+                        {% if event.is_in_past %}
+                        <li class="if-js"><a class="jump-link" id="add-attendees-link" href="#attendees-add">Add Attendees</a></li>
+                        {% endif %}
                         {% if event.is_open_for_signup and page.pagefollowup.send_taf %}
                         <li class="if-js"><a class="jump-link" id="invite-friends-link" href="#invite-friends">Invite friends</a></li>
                         {% endif %}
@@ -215,6 +233,7 @@
                 <!-- Optional no attendees message, e.g., "go use the Invite Folks tool at right" -->
             {% endif %}
 
+            {% include './event_roster_add.html' %}
             <!-- Invite -->
             {% if event.is_open_for_signup %}
                 <div class="ak-margin-top-2 ak-clearfix">

Remove autoescape tags from thank you text

diff --git a/db_templates/Original/thanks.html b/db_templates/Original/thanks.html
index ee4c128ea..40511e04b 100644
--- a/db_templates/Original/thanks.html
+++ b/db_templates/Original/thanks.html
@@ -11,7 +11,7 @@
         <div class="ak-grid-row">
             <div class="ak-grid-col ak-grid-col-12-of-12">
                 <p>
-                    {% filter ak_text:"logged_in_as" %}You are logged in as{% endfilter %} {{ actionkit_user.name }}.
+                    {% filter ak_text:"logged_in_as" %}You are logged in as{% endfilter %} {{ actionkit_user.name|escape }}.
                     <a href="/logout">{% filter ak_text:"logout" %}Click to log out.{% endfilter %}</a>
                 </p>
             </div>

2.3.22 (2017 November 27)

No changes.

2.3.21 (2017 November 15)

Add sharing buttons to event templates

diff --git a/db_templates/Original/event_attendee_taf_msg.txt b/db_templates/Original/event_attendee_taf_msg.txt
index 48f4f4a52..91454c9e2 100644
--- a/db_templates/Original/event_attendee_taf_msg.txt
+++ b/db_templates/Original/event_attendee_taf_msg.txt
@@ -13,7 +13,7 @@ on {{ event.starts_at|date:"l, F j" }} at {{ event.starts_at|date:"f A" }}.
 
 RSVP here to join me:
 
-http://{% client_domain %}/event/{{ event.campaign.signup_page.name }}/{{ event.id }}/?source=taf
+{% client_domain_url %}event/{{ event.campaign.signup_page.name }}/{{ event.id }}/?source=taf
 
 Thanks,
 {{ action.user.first_name }}
diff --git a/db_templates/Original/event_email_cancelled.html b/db_templates/Original/event_email_cancelled.html
index 0c1b758e6..6cc5c7335 100644
--- a/db_templates/Original/event_email_cancelled.html
+++ b/db_templates/Original/event_email_cancelled.html
@@ -5,7 +5,7 @@ Subject: Your "{{campaign_title}}" event has been cancelled
 
 <p>You can search for other events here:</p>
 
-<p><a href="http://{% client_domain %}/event/{{ campaign.local_name }}/search/">http://{% client_domain %}/event/{{ campaign.local_name }}/search/</a></p>
+<p><a href="{% client_domain_url %}event/{{ campaign.local_name }}/search/">{% client_domain_url %}event/{{ campaign.local_name }}/search/</a></p>
 
 <!-- Replacement for unsubscribe. -->
 <p><small>
diff --git a/db_templates/Original/event_host_details.html b/db_templates/Original/event_host_details.html
index 77f7e0717..31c9feb77 100644
--- a/db_templates/Original/event_host_details.html
+++ b/db_templates/Original/event_host_details.html
@@ -43,7 +43,7 @@
 {% endif %}
     <tr class="ak-event-signup-link">
         <th>Signup link:</th>
-        <td> <a href="http://{% client_domain %}{{ event.public_url }}">http://{% client_domain %}{{ event.public_url }}</a></td>
+        <td> <a href="{% client_domain_url event.public_url %}">{% client_domain_url event.public_url %}</a></td>
     </tr>
 </table>
 
diff --git a/db_templates/Original/event_host_taf_msg.txt b/db_templates/Original/event_host_taf_msg.txt
index 092bb7f74..14ea29106 100644
--- a/db_templates/Original/event_host_taf_msg.txt
+++ b/db_templates/Original/event_host_taf_msg.txt
@@ -13,7 +13,7 @@ on {{ event.starts_at|date:"l, F j" }} at {{ event.starts_at|date:"f A" }}.
 
 RSVP here so I can know you're coming:
 
-http://{% client_domain %}/event/{{ event.campaign.signup_page.name }}/{{ event.id }}/?source=taf
+{% client_domain_url %}event/{{ event.campaign.signup_page.name }}/{{ event.id }}/?source=taf
 
 Thanks,
 {{ action.user.first_name }}
diff --git a/db_templates/Original/event_email_approved.html b/db_templates/Original/event_email_approved.html
index 309daa415..3bc42c4ce 100644
--- a/db_templates/Original/event_email_approved.html
+++ b/db_templates/Original/event_email_approved.html
@@ -1,15 +1,17 @@
 Subject: Your "{{campaign_title}}" event has been approved!
 
 {% load actionkit_tags %}
 <!-- If a campaign requires event approval, this email gets sent out to event hosts when their event is approved -->
 
 <p>Congratulations! Your "{{campaign_title}}" event has been approved and is now open for signups!</p>
 
-<p>You can start sharing your event at {% endfilter %} <a href="http://{% client_domain %}{{ event.public_url }}">http://{% client_domain %}{{ event.public_url }}</a> with potential attendees. </p> {% filter akid:user|number_links|cleanup_links %}
+{% filter tag_links:'nr=1' %}
+<p>You can start sharing your event at <a href="{% client_domain_url event.public_url %}">{% client_domain_url event.public_url %}</a> with potential attendees. </p>
+{% endfilter %}
 
 <p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}">https://{% client_ssl_domain %}{{ signup.url }}</a></p>
 
 <p>Thanks again!</p>
 
 <!-- Replacement for unsubscribe -->
 <p><small>You're receiving this message because you set up a "{{campaign_title}}" event.  If you don't want to receive further emails about this event, you can cancel the event using the host tools linked above.</small></p>
diff --git a/db_templates/Original/event_email_approved.html b/db_templates/Original/event_email_approved.html
index 309daa415..3bc42c4ce 100644
--- a/db_templates/Original/event_email_approved.html
+++ b/db_templates/Original/event_email_approved.html
@@ -1,15 +1,17 @@
 Subject: Your "{{campaign_title}}" event has been approved!
 
 {% load actionkit_tags %}
 <!-- If a campaign requires event approval, this email gets sent out to event hosts when their event is approved -->
 
 <p>Congratulations! Your "{{campaign_title}}" event has been approved and is now open for signups!</p>
 
-<p>You can start sharing your event at {% endfilter %} <a href="{% client_domain_url event.public_url %}">{% client_domain_url event.public_url %}</a> with potential attendees. </p> {% filter akid:user|number_links|cleanup_links %}
+{% filter tag_links:'nr=1' %}
+<p>You can start sharing your event at <a href="{% client_domain_url event.public_url %}">{% client_domain_url event.public_url %}</a> with potential attendees. </p>
+{% endfilter %}
 
 <p>To use your event tools, visit <a href="https://{% client_ssl_domain %}{{ signup.url }}">https://{% client_ssl_domain %}{{ signup.url }}</a></p>
 
 <p>Thanks again!</p>
 
 <!-- Replacement for unsubscribe -->
 <p><small>You're receiving this message because you set up a "{{campaign_title}}" event.  If you don't want to receive further emails about this event, you can cancel the event using the host tools linked above.</small></p>

2.3.20 (2017 October 25)

No changes.

2.3.19 (2017 October 2)

No changes.

2.3.18 (2017 September 20)

No changes.

2.3.17 (2017 September 7)

Use {% include_tmpl %} to include form details on event pages

diff --git a/db_templates/Original/event_host_tools.html b/db_templates/Original/event_host_tools.html
index 8a5af55..495dcd6 100644
--- a/db_templates/Original/event_host_tools.html
+++ b/db_templates/Original/event_host_tools.html
@@ -141,7 +141,7 @@
 
             <!-- Message from mothership -->
             <div id="host-tools-intro">
-                {{ form.tools_text|safe }}
+                {% include_tmpl form.tools_text %}
             </div>
 
         </div>
@@ -158,7 +158,7 @@
                        <span class="ak-error">Note: this event is awaiting approval by staff.</span>
                     {% endif %}
                     <ul id="event-host-links">
-                        {{ form.tools_sidebar|safe }}
+                        {% include_tmpl form.tools_sidebar %}
                         {% if user_is_manager %}
                         <li>Logged in as a manager 
                             {% if campaign.require_email_confirmation and not event.host_is_confirmed %}
diff --git a/db_templates/Original/event_attend.html b/db_templates/Original/event_attend.html
--- a/db_templates/Original/event_attend.html
+++ b/db_templates/Original/event_attend.html
@@ -66,7 +79,7 @@ $( function () {
             <div class="ak-grid-col {% if page.custom_fields.featured_image %}ak-grid-col-9-of-12{% else %}ak-grid-col-12-of-12{% endif %}">
                 {% if not update and form.signup_text %}
                     <div class="ak-page-content">
-                        {{ form.signup_text|safe }}
+                        {% include_tmpl form.signup_text %}
                     </div>
diff --git a/db_templates/Original/event_attendee_tools.html b/db_templates/Original/event_attendee_tools.html
index 4d89f60..12f36e5 100644
--- a/db_templates/Original/event_attendee_tools.html
+++ b/db_templates/Original/event_attendee_tools.html
@@ -21,7 +21,7 @@
 
         <!-- Message from mothership -->
         <div id="attendee-tools-intro">
-            {{ form.tools_text|safe }}
+            {% include_tmpl form.tools_text %}
         </div>
 
     </div><!--col-12-->
@@ -31,7 +31,7 @@
     <div class="ak-grid-col ak-grid-col-6-of-12">
         <h3>Event tools</h3>
         <ul id="event-attendee-links" class="ak-links-menu">
-            {{ form.tools_sidebar|safe }}
+            {% include_tmpl form.tools_sidebar %}
             <li><a href="../cancel_signup/?akid={{ args.akid }}">Cancel signup</a></li> 
             <li class="narrower"> 
             | <a href="#" onclick="$('div.contact-form').slideDown('slow'); $(this).parent('li.narrower').hide();">Email host</a>
diff --git a/db_templates/Original/event_search.html b/db_templates/Original/event_search.html
index d476558..7b8c95d 100644
--- a/db_templates/Original/event_search.html
+++ b/db_templates/Original/event_search.html
@@ -34,7 +34,7 @@
         <div class="ak-grid-row">
             <div class="ak-grid-col ak-grid-col-12-of-12">
                 {% if form.search_page_text %}
-                    <div class="area">{{ form.search_page_text|safe }}</div>
+                    <div class="area">{% include_tmpl form.search_page_text %}</div>
                 {% endif %}
 
                 <ul class="compact" id="ak-errors"></ul>

2.3.16 (2017 August 29)

No changes.

2.3.14 (2017 July 19)

Fix country select template

diff --git a/db_templates/Original/recurring_update.html b/db_templates/Original/recurring_update.html
index 0d659ad..b1871c0 100644
--- a/db_templates/Original/recurring_update.html
+++ b/db_templates/Original/recurring_update.html
@@ -147,6 +147,10 @@ window.actionkitBeforeSubmit = function() {
     else {
         return;
     }
+    // user might just be changing amount
+    if ( !$form.find('[name=card_num]').is(':visible') )
+        return;
+    
     var $expField = $form.find('[name=exp_date]');
     var exp = $expField.val();
     // Stripe wants exp as 12/99 not 1299

2.3.13 (2017 June 28)

Don't assume "Monthly" is the only recurring donation period

diff --git a/db_templates/Original/recurring_info.html b/db_templates/Original/recurring_info.html
index 189b57c..e560d67 100644
--- a/db_templates/Original/recurring_info.html
+++ b/db_templates/Original/recurring_info.html
@@ -24,7 +24,7 @@
         
         <div class="ak-show-amount">
             <label>
-                Monthly Amount
+                {{ profile.get_period_display }} Amount
             </label>
             <div class="ak-readonly-value">
                 <div>
@@ -37,7 +37,7 @@
         </div>
         <div class="ak-change-amount" style="display: none">
             <label>
-                Monthly Amount
+                {{ profile.get_period_display }} Amount
             </label>
             <input type="text" name="amount" size="6" value="{{ profile.amount }}">
         </div>

2.3.12 (2017 June 22)

Don't double-escape the tell-a-friend mailto on the after-action page

diff --git a/db_templates/Original/thanks.html b/db_templates/Original/thanks.html
index a9d4330..8847e80 100644
--- a/db_templates/Original/thanks.html
+++ b/db_templates/Original/thanks.html
@@ -68,7 +68,7 @@
 
             {% if page.followup.send_taf and args.taf %}
                 <div>
-                    <a class="ak-button ak-share-button ak-email" href="mailto:?subject={{ page.followup.taf_subject|urlencode }}&amp;body={% filter referring_akid:akid|tag_links:"source=mailto"|urlencode %}{% include_tmpl page.followup.taf_body escaped %}{% endfilter %}" target="_blank">{% filter ak_text:"taf_mailto_ask" %}Send an email now {% endfilter %}</a>
+                    <a class="ak-button ak-share-button ak-email" href="mailto:?subject={{ page.followup.taf_subject|urlencode }}&amp;body={% filter referring_akid:akid|tag_links:"source=mailto"|urlencode %}{% include_tmpl page.followup.taf_body %}{% endfilter %}" target="_blank">{% filter ak_text:"taf_mailto_ask" %}Send an email now {% endfilter %}</a>
                 </div>
 
                 <div id="copy-and-paste">

2.3.11 (2017 May 17)

Show global-friendly region (state) and postal (zip) fields when needed on event pages

diff --git a/db_templates/Original/user_form.html b/db_templates/Original/user_form.html
index 602f119..15ac03a 100644
--- a/db_templates/Original/user_form.html
+++ b/db_templates/Original/user_form.html
@@ -45,9 +45,15 @@
 {% if 'country'|is_in:fields %}
     <input type="hidden" name="auto_country" value="1">
     <style type="text/css">
-        /* Ensure that, if there's no JavaScript, the global-friendly 
+        /* Ensure that, if there's no JavaScript, and there are both
+         * global-friendly and US-only fields, the global-friendly
          * fields show rather than the US-only ones */
-        #ak-fieldbox-zip, #ak-fieldbox-state { display: none; }
+        {% if 'postal'|is_in:fields %}
+        #ak-fieldbox-zip { display: none; }
+        {% endif %}
+        {% if 'region'|is_in:fields %}
+        #ak-fieldbox-state { display: none; }
+        {% endif %}
     </style>
 {% else %}
     <input type="hidden" name="country" value="United States">

Only show title on the Attendee Details page when there is a title

diff --git a/db_templates/Original/event_attendee_details.html b/db_templates/Original/event_attendee_details.html
index 7514f8a..9091d72 100644
--- a/db_templates/Original/event_attendee_details.html
+++ b/db_templates/Original/event_attendee_details.html
@@ -4,7 +4,7 @@
 
 <div class="ak-field-box">
 
-    {% if campaign.use_title and campaign.show_title %}
+    {% if campaign.use_title and campaign.show_title and event.title %}
         <p class="ak-event-title"><a href="/event/{{ campaign.local_name }}/{{event.id}}/signup/?akid={{args.akid}}&amp;zip={{args.zip}}">{{ event.title }}</a></p>
     {% endif %}

2.3.10 (2017 March 15)

Reflect country change on the recurring info page

diff --git a/db_templates/Original/recurring_info.html b/db_templates/Original/recurring_info.html
index a90939f..189b57c 100644
--- a/db_templates/Original/recurring_info.html
+++ b/db_templates/Original/recurring_info.html
@@ -174,6 +174,7 @@
                      {% if profile.order.user_detail.state %}
                        $("#change_profile_{{ profile.id }} .state_select_box select").val("{{ profile.order.user_detail.state }}");
                      {% endif %}
+                     actionkit.forms.reflectCountryChange();
                   });
                 </script>
             </div>

2.3.9 (2017 February 28)

Remove the "Truncate / Read More" feature

diff --git a/db_templates/Original/wrapper.html b/db_templates/Original/wrapper.html
index b9fbad5..f87aaa8 100644
--- a/db_templates/Original/wrapper.html
+++ b/db_templates/Original/wrapper.html
@@ -283,37 +283,6 @@
     </script>
     {% endblock %}
 
-    <script type="text/javascript">
-        $(window).load(function() {
-            function truncate_read_more() {
-                var ak_text_expander_height = $('.ak-text-expander').height();
-                $('.ak-text-expander').css('max-height','4.5em');
-                var ak_text_expander_height_truncated = $('.ak-text-expander').height();
-                //console.log(ak_text_expander_height, 'original height');
-                //console.log(ak_text_expander_height_truncated, 'new height');
-                if (ak_text_expander_height !== ak_text_expander_height_truncated) {
-                    $('.ak-text-expander').addClass('ak-truncated');
-                }
-                if ($(window).width() >= 480){    
-                    $('.ak-text-expander').css('max-height','none');
-                    $('a.ak-read-more').hide();
-                } else {
-                    $('.ak-text-expander.ak-truncated + a.ak-read-more').show();
-                }
-                    
-                $('.ak-read-more').on('click', function() {
-                    $('.ak-text-expander').css('max-height','100%')
-                    .css('margin-bottom','10px');
-                    $(this).hide();
-                });
-            }
-            if ($('.ak-text-expander').length) {
-                $(window).on('resize', truncate_read_more);
-                truncate_read_more();
-            }
-        });
-    </script>
-
     {% if analytics_key %}
     <script type="text/javascript">
     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){

2.3.8 (2017 February 9)

No changes.

2.3.7 (2017 February 2)

No changes.

2.3.6 (2017 January 12)

2.3.5 (2016 December 14)

Adjustments to country/state display on User Update screen

Index: db_templates/Original/user_update.html
===================================================================
--- a/db_templates/Original/user_update.html 
+++ b/db_templates/Original/user_update.html
@@ -39,13 +39,69 @@ 
                     {% switch field.name %}
                         {% case 'country' %}
-                             <select name="{{ input_name_prefix }}country" id="id_{{ input_name_prefix }}country" {% if onchange %}onchange="{{ onchange }}" onblur="{{ onchange }}"{% endif %}>
+                               <select name="{{ input_name_prefix }}country" id="id_{{ input_name_prefix }}country" {% if onchange %}onchange="{{ onchange }}" onblur="{{ onchange }}"{% endif %}>
+                                <option selected="{{ field.value }}">{{ field.value }}</option>
                                 {% for std_name,name in templateset.lang.country_names_us_first %}
                                     <option value="{{ std_name|escapeall }}">{{ name|escapeall }}</option>
                                 {% endfor %}
                             </select>
                         {% case 'state' %} 
-                            {% include "./state_select.html" %}
+                                                       <select name="{{ input_name_prefix }}state" id="id_{{ input_name_prefix }}state" {% if onchange %}onchange="{{ onchange }}" onblur="{{ onchange }}"{% endif %}>
+                                                       <option selected="{{ field.value }}">{{ field.value }}</option>
+                                                       <option value="AL">Alabama</option>
+                                                               <option value="AK">Alaska</option>
+                                                               <option value="AZ">Arizona</option>
+                                                               <option value="AR">Arkansas</option>
+                                                               <option value="CA">California</option>
+                                                               <option value="CO">Colorado</option>
+                                                               <option value="CT">Connecticut</option>
+                                                               <option value="DE">Delaware</option>
+                                                               <option value="DC">District of Columbia</option>
+                                                               <option value="FL">Florida</option>
+                                                               <option value="GA">Georgia</option>
+                                                               <option value="HI">Hawaii</option>
+                                                               <option value="ID">Idaho</option>
+                                                               <option value="IL">Illinois</option>
+                                                               <option value="IN">Indiana</option>
+                                                               <option value="IA">Iowa</option>
+                                                               <option value="KS">Kansas</option>
+                                                               <option value="KY">Kentucky</option>
+                                                               <option value="LA">Louisiana</option>
+                                                               <option value="ME">Maine</option>
+                                                               <option value="MD">Maryland</option>
+                                                               <option value="MA">Massachusetts</option>
+                                                               <option value="MI">Michigan</option>
+                                                               <option value="MN">Minnesota</option>
+                                                               <option value="MS">Mississippi</option>
+                                                               <option value="MO">Missouri</option>
+                                                               <option value="MT">Montana</option>
+                                                               <option value="NE">Nebraska</option>
+                                                               <option value="NV">Nevada</option>
+                                                               <option value="NH">New Hampshire</option>
+                                                               <option value="NJ">New Jersey</option>
+                                                               <option value="NM">New Mexico</option>
+                                                               <option value="NY">New York</option>
+                                                               <option value="NC">North Carolina</option>
+                                                               <option value="ND">North Dakota</option>
+                                                               <option value="OH">Ohio</option>
+                                                               <option value="OK">Oklahoma</option>
+                                                               <option value="OR">Oregon</option>
+                                                               <option value="PA">Pennsylvania</option>
+                                                               <option value="RI">Rhode Island</option>
+                                                               <option value="SC">South Carolina</option>
+                                                               <option value="SD">South Dakota</option>
+                                                               <option value="TN">Tennessee</option>
+                                                               <option value="TX">Texas</option>
+                                                               <option value="UT">Utah</option>
+                                                               <option value="VT">Vermont</option>
+                                                               <option value="VA">Virginia</option>
+                                                               <option value="WA">Washington</option>
+                                                               <option value="DC">Washington, D.C.</option>
+                                                               <option value="WV">West Virginia</option>
+                                                               <option value="WI">Wisconsin</option>
+                                                               <option value="WY">Wyoming</option>
+                                                       </select>
+
                         {% else %}
                                 {{ field }}
                     {% endswitch %}

2.3.4 (2016 November 30)

No changes.

2.3.3 (2016 November 15)

Use 'keyup' event instead of 'keypress' to clear radio buttons on donate pages

Index: db_templates/Original/donate.html
===================================================================
--- db_templates/Original/donate.html (revision 999)
+++ db_templates/Original/donate.html (working copy)
@@ -538,7 +538,7 @@
                                 .on('click', clear_other)
                                 .on('click', update_total);
         $('#ak-other-amount-field').on('change blur', update_total)
-                                .on('click keypress', clear_radio_buttons)
+                                .on('click keyup', clear_radio_buttons)
                                 .on('click', update_total);
     });
 

2.3.2 (2016 October 19)

No changes.

2.3.1 (2016 October 11)

Original templateset released; previous Original renamed to Original (before Fall 2016).