Skip to content

Commit

Permalink
add signature capture to web form (furthemore#323)
Browse files Browse the repository at this point in the history
* add sig capture for coc to reg form

* add signature to dealers

* add signature to badge admin

* update base image

* add square tag to tests

* magic test fix

* remove old requirements
  • Loading branch information
meanderfox authored and anadon committed Dec 23, 2024
1 parent d840e64 commit 6c2123c
Show file tree
Hide file tree
Showing 17 changed files with 192 additions and 115 deletions.
4 changes: 2 additions & 2 deletions DockerfileBase
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ COPY ./requirements.txt /app
WORKDIR /app

RUN apt-get update && apt-get -y install python3-psycopg2 ca-certificates xfonts-75dpi xfonts-base
RUN wget -nv https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.buster_amd64.deb
RUN dpkg -i wkhtmltox_0.12.6-1.buster_amd64.deb
RUN wget -nv https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.bullseye_amd64.deb
RUN dpkg -i wkhtmltox_0.12.6.1-3.bullseye_amd64.deb
RUN apt-get -y -f install

RUN pip install --upgrade pip
Expand Down
6 changes: 5 additions & 1 deletion registration/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1148,7 +1148,7 @@ class BadgeAdmin(NestedModelAdmin, ImportExportModelAdmin):
"badgeName",
"badgeNumber",
]
readonly_fields = ["get_age_range", "registrationToken"]
readonly_fields = ["get_age_range", "registrationToken", "image_signature"]
actions = [
assign_badge_numbers,
print_badges,
Expand All @@ -1165,12 +1165,16 @@ class BadgeAdmin(NestedModelAdmin, ImportExportModelAdmin):
"printed",
("badgeName", "badgeNumber", "get_age_range"),
("registeredDate", "event", "registrationToken"),
"image_signature",
"attendee",
)
},
),
)

def image_signature(self, obj):
return format_html('<img src="data:image/png;base64,{}">', obj.signature_bitmap)

def get_age_range(self, obj):
try:
born = obj.attendee.birthdate
Expand Down
28 changes: 28 additions & 0 deletions registration/migrations/0101_auto_20240630_0045.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 3.2.25 on 2024-06-30 04:45

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('registration', '0100_webhook_type'),
]

operations = [
migrations.AlterField(
model_name='badge',
name='signature_bitmap',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='badge',
name='signature_svg',
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name='order',
name='status',
field=models.CharField(choices=[('Pending', 'Pending'), ('Captured', 'Captured'), ('Completed', 'Completed'), ('Refunded', 'Refunded'), ('Refund Pending', 'Refund Pending'), ('Failed', 'Failed'), ('Dispute Evidence Required', 'Dispute Evidence Required'), ('Dispute Processing', 'Dispute Processing'), ('Dispute Won', 'Dispute Won'), ('Dispute Lost', 'Dispute Lost'), ('Dispute Accepted', 'Dispute Accepted')], default='Pending', max_length=50),
),
]
8 changes: 2 additions & 6 deletions registration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,12 +428,8 @@ class Badge(models.Model):
badgeNumber = models.IntegerField(null=True, blank=True)
printed = models.BooleanField(default=False)
printCount = models.IntegerField(default=0)
signature_svg = models.FileField(
upload_to=badge_signature_svg_path, null=True, blank=True
)
signature_bitmap = models.ImageField(
upload_to=badge_signature_bitmap_path, null=True, blank=True
)
signature_svg = models.TextField(null=True, blank=True)
signature_bitmap = models.TextField(null=True, blank=True)

def __str__(self):
if self.badgeNumber is not None or self.badgeNumber == "":
Expand Down
83 changes: 83 additions & 0 deletions registration/static/js/jSignature.min.js

Large diffs are not rendered by default.

26 changes: 12 additions & 14 deletions registration/templates/registration/dealer/dealer-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -206,19 +206,7 @@ <h2>Placement Options</h2>
</div>
</div>
<hr/>
<div class="form-group">
<div class="checkbox col-sm-12">
<label>
<input type="checkbox" id="agreeToRules" name="agreeToRules" class="form-control-checkbox"
required data-error="You must agree to the event code of conduct to register.">
I have read and understand the rules pertaining to both {{ event.name }} and the Markeplace. I agree to
abide by the {{ event.name }} <a href="{{ event.codeOfConduct }}" target="_blank">Code of Conduct</a>. By
registering for {{ event }} you attest that you are not listed on any sexual offender registry.
</label>
</div>
<div class="col-sm-offset-3 help-block with-errors" style=" padding-left:15px;"></div>
</div>

{% include "templatetags/coc_sig.html" %}

<div class="form-group">
<div class="col-sm-9 col-sm-offset-3">
Expand Down Expand Up @@ -335,6 +323,14 @@ <h2>Placement Options</h2>
return;
}

let signature = $("#jsign_signature");
let nativeData = signature.jSignature("getData", "native");
if (nativeData.length < 2) {
alert("You must sign to acknowledge your acceptance of the Code of Conduct.");
$("#register").one('click', register_click);
return;
}

var data = {
'attendee': {
'firstName': $("#firstName").val(),
Expand All @@ -351,7 +347,9 @@ <h2>Placement Options</h2>
'birthdate': $("#birthDate").val(),
'emailsOk': $("#contact").is(':checked'),
'surveyOk': $("#survey").is(':checked'),
'badgeName': $("#badgename").val()
'badgeName': $("#badgename").val(),
'signature_svg': signature.jSignature("getData", "svgbase64")[1],
'signature_bitmap': signature.jSignature("getData", "image")[1]
},
'dealer': {
'businessName': $("#businessName").val(), 'website': $("#website").val(), 'logo': $("#logo").val(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,7 @@ <h3 class="align-right" style="margin-top: 0; color: #333333; font-size: 14px; f
{% if hasMinors %}
<p style="font-size: 16px; line-height: 1.625; color: #51545E; margin: .4em 0 1.1875em;">One or more of the attendees in this order will still be minors at the start of {{ event }}. Please note that we require a parent or guardian to be present at registration to sign our consent form for all minor attendees. Any attendee under the age of 13 (by the start of the convention) must have a parent or guardian also register and accompany them during the event.</p>
{% endif %}
<p style="font-size: 16px; line-height: 1.625; color: #51545E; margin: .4em 0 1.1875em;">This email is for your records only. You will not need the confirmation number to pick up your badge. In order to pick up your badge on-site, you will only need to bring your Government Issued Photo ID and Vaccine proof to the registration desk.</p>
<p style="font-size: 16px; line-height: 1.625; color: #51545E; margin: .4em 0 1.1875em;">This email is for your records only. You will not need the confirmation number to pick up your badge. In order to pick up your badge on-site, you will only need to bring your Government Issued Photo ID to the registration desk.</p>
<p style="font-size: 16px; line-height: 1.625; color: #51545E; margin: .4em 0 1.1875em;">If you have any questions about your order, please contact us at <a href="mailto:{{ event.registrationEmail }}" style="color: #3869D4;">{{ event.registrationEmail }}</a></p>
<p style="font-size: 16px; line-height: 1.625; color: #51545E; margin: .4em 0 1.1875em;">Thanks,
<br />{{ event }} Registration Team</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ consent form for all minor attendees. Any attendee under the age of 13 (by the s
must have a parent or guardian also register and accompany them during the event.
{% endif %}

This email is for your records only. You will not need the confirmation number to pick up your badge. In order to pick up your badge on-site, you will only need to bring your Government Issued Photo ID and Vaccine proof to the registration desk.
This email is for your records only. You will not need the confirmation number to pick up your badge. In order to pick up your badge on-site, you will only need to bring your Government Issued Photo ID to the registration desk.

If you have any questions about your order, please contact us at {{ event.registrationEmail }}.

Expand Down
1 change: 1 addition & 0 deletions registration/templates/registration/master.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<script src="{% static 'js/bootstrap-datepicker.js' %}"></script>
<script src="{% static 'js/validator.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>
<script src="{% static 'js/jSignature.min.js' %}"></script>

{% block javascript %}
{% endblock %}
Expand Down
28 changes: 14 additions & 14 deletions registration/templates/registration/registration-form.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ <h1>Pre-Register for {{ event }}!</h1>
<h3>Select your registration options and level.</h3>
<hr>
<div class="form-group">
<label for="firstName" class="col-sm-3 control-label">Badge Name <span style="color:red;">*</span></label>
<label for="badgeName" class="col-sm-3 control-label">Badge Name <span style="color:red;">*</span></label>
<div class="col-sm-9">
<input type="text" id="badgeName" name="badgeName" placeholder="Your Name Here.."
class="form-control form-control-text" maxlength="25" autocomplete="off" required />
</div>
<div class="col-sm-offset-3 help-block with-errors" style=" padding-left:15px;"></div>
</div>
<div class="form-group">
<label for="firstName" class="col-sm-3 control-label">Are you interested in volunteering?</label>
<label for="volunteer" class="col-sm-3 control-label">Are you interested in volunteering?</label>
<div class="col-sm-9">
<select id="volunteer" name="volunteer" class="form-control form-control-select">
<option value="">No Thanks</option>
Expand All @@ -80,18 +80,8 @@ <h3>Select your registration options and level.</h3>
</div>
<br/>
<hr/>
<div class="form-group">
<div class="checkbox col-sm-12">
<label>
<input class="form-control form-control-checkbox" data-error="You must agree to the code of conduct to register." id="agreeToRules" name="agreeToRules"
required type="checkbox"/>
I agree to abide by the {{ event.name }} <a href="{{ event.codeOfConduct }}" target="_blank">Code of
Conduct</a>. By registering for {{ event }} you attest that you are not listed on any sexual offender
registry.
</label>
</div>
<div class="col-sm-offset-3 help-block with-errors" style=" padding-left:15px;"></div>
</div>

{% include "templatetags/coc_sig.html" %}
<div class="container-fluid">
<button class="btn btn-primary col-sm-6 pull-right" id="register" type="submit">Register</button> &nbsp;
</div>
Expand Down Expand Up @@ -422,6 +412,14 @@ <h3 data-content="name"></h3>
}
let birthdate = parseDate(birthdate_str);

let signature = $("#jsign_signature");
let nativeData = signature.jSignature("getData", "native");
if (nativeData.length < 2) {
alert("You must sign to acknowledge your acceptance of the Code of Conduct.");
$("#register").one('click', register_click);
return;
}

{% if not event.allowOnlineMinorReg %}
let age = getAgeByEventStart(birthdate);
if (age < 18) {
Expand Down Expand Up @@ -449,6 +447,8 @@ <h3 data-content="name"></h3>
'volDepts': $("#volunteer").val(),
'surveyOk': $("#survey").is(':checked'),
'asl': false,
'signature_svg': signature.jSignature("getData", "svgbase64")[1],
'signature_bitmap': signature.jSignature("getData", "image")[1]
},
'priceLevel': {'id': $(".selectLevel")[0].id.split('_')[1], 'options': getOptions()},
'event': '{{event}}'
Expand Down
48 changes: 0 additions & 48 deletions registration/templates/registration/signature.html

This file was deleted.

33 changes: 33 additions & 0 deletions registration/templates/templatetags/coc_sig.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% load registration_tags static %}

<div class="form-group">
<div class="checkbox col-sm-12">
<label>
<input type="checkbox" id="agreeToRules" name="agreeToRules" class="form-control form-control-checkbox"
required data-error="You must agree to the code of conduct to register." />
I agree to abide by the {{ event.name }} <a href="{{ event.codeOfConduct }}" target="_blank">Code of
Conduct</a>. By registering for {{ event }} you attest that you are not listed on any sexual offender
registry.
</label>
</div>
<div class="col-sm-offset-3 help-block with-errors" style=" padding-left:15px;"></div>
</div>
<div>Please Sign Below:</div>
<div class='jsign-container'
data-config='{"width": "300px", "height": "200px", "color": "#000", "background-color": "#FFF", "decor-color": "#DDD", "lineWidth": 0, "UndoButton": false, "ResetButton": true}'
data-initial-value='[]'
id='jsign_signature'
style="border: dashed darkgray; margin-bottom: 10px">
</div>

<input type='button' value='Clear Signature' class="btn btn-xs" style="margin-bottom: 20px">

<script>
window.addEventListener("load", () => {
$("#jsign_signature").jSignature();
$("input[value='Clear Signature']").on("click", function() {
$("#jsign_signature").jSignature('clear');
})
})

</script>
1 change: 1 addition & 0 deletions registration/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ def setUp(self):
"volunteer": "false",
"volDepts": "",
"surveyOk": "false",
"signature_svg": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4xIiB3aWR0aD0iNjI3IiBoZWlnaHQ9IjkwIj48cGF0aCBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlPSJyZ2IoODUsIDg1LCA4NSkiIGZpbGw9Im5vbmUiIGQ9Ik0gMSA1NSBjIDAuMDUgLTAuMjEgMS4yIC04LjU5IDMgLTEyIGMgNC4yIC03Ljk1IDEwLjE2IC0xNi41NSAxNiAtMjQgYyAzLjcyIC00Ljc1IDguNDYgLTkuMjkgMTMgLTEzIGMgMi41NCAtMi4wOCA2LjE1IC0zLjk4IDkgLTUgYyAxLjM4IC0wLjQ5IDMuNzkgLTAuNjEgNSAwIGMgMi4yNCAxLjEyIDYuMzYgMy42IDcgNiBjIDMgMTEuMzEgNC40OSAyOC4zNiA2IDQzIGMgMC44NyA4LjQ0IDAuMjcgMTYuNzcgMSAyNSBjIDAuMjcgMy4wMyAxLjAzIDYuMjcgMiA5IGMgMC42MiAxLjczIDEuNjYgNC44MiAzIDUgYyA2LjE5IDAuODMgMTguMjMgMC41MyAyNyAtMSBjIDI1LjI5IC00LjQyIDQ5LjY3IC0xMS40NCA3NiAtMTcgYyAxMS4zNCAtMi4zOSAyMS44MSAtNC40IDMzIC02IGMgNS4zNSAtMC43NiAxMC41IC0wLjg2IDE2IC0xIGMgNy41NCAtMC4yIDE1LjIxIC0wLjk0IDIyIDAgYyA0LjU5IDAuNjQgOS40NyAyLjk5IDE0IDUgYyA0LjUgMiA4Ljc0IDUuMiAxMyA3IGMgMS43NiAwLjc0IDMuOTggMC45MyA2IDEgYyA4LjI5IDAuMjcgMTYuNjggMC41NSAyNSAwIGMgNi43MyAtMC40NSAyMCAtMyAyMCAtMyIvPjxwYXRoIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9InJnYig4NSwgODUsIDg1KSIgZmlsbD0ibm9uZSIgZD0iTSAyMDUgMzggYyAwLjI1IDAgOS4zOCAwLjQ5IDE0IDAgYyA4LjAyIC0wLjg0IDE1LjgzIC0zLjIzIDI0IC00IGMgMTMuNDQgLTEuMjYgMjYuMjEgLTEuODQgNDAgLTIgYyA0NC4wOCAtMC41MiA4NC44OCAtMS43NSAxMjggMCBjIDIzLjQgMC45NSA0NS41NiA0LjMgNjkgOCBjIDI4LjUzIDQuNSA1NS4wMyAxMC4xNyA4MyAxNiBjIDQuNSAwLjk0IDguNTMgMy4xNSAxMyA0IGMgMTYuNjMgMy4xNyA1MCA4IDUwIDgiLz48L3N2Zz4=",
}
self.attendee_form_2 = {
"firstName": "Bea",
Expand Down
9 changes: 8 additions & 1 deletion registration/tests/test_webhooks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from unittest.mock import patch

from django.test import TestCase
from django.test import TestCase, tag
from django.test.utils import override_settings
from django.urls import reverse

Expand Down Expand Up @@ -31,6 +31,7 @@ class TestSquareRefundWebhooks(TestCase):
SIGNATURE_KEY = "bj-4rZKxCc8_1CZtghoatg"
NOTIFICATION_URL = "https://webhook.site/5477eda8-952e-4844-91db-8b10cf228833"

@tag("square")
@override_settings(SQUARE_WEBHOOK_SIGNATURE_KEY=SIGNATURE_KEY)
@patch("django.http.request.HttpRequest.build_absolute_uri")
def test_square_webhook(self, mock_build_absolute_uri):
Expand All @@ -48,6 +49,7 @@ def test_square_webhook(self, mock_build_absolute_uri):
self.assertEqual(str(webhook.event_id), webhook.body["event_id"])
self.assertEqual(webhook.event_type, webhook.body["type"])

@tag("square")
@override_settings(SQUARE_WEBHOOK_SIGNATURE_KEY=SIGNATURE_KEY)
@patch("django.http.request.HttpRequest.build_absolute_uri")
def test_square_webhook_invalid_signature(self, mock_build_absolute_uri):
Expand All @@ -62,6 +64,7 @@ def test_square_webhook_invalid_signature(self, mock_build_absolute_uri):

self.assertTrue(response.status_code, 403)

@tag("square")
@override_settings(SQUARE_WEBHOOK_SIGNATURE_KEY=SIGNATURE_KEY)
@patch("square.utilities.webhooks_helper.is_valid_webhook_event_signature")
@patch("django.http.request.HttpRequest.build_absolute_uri")
Expand All @@ -81,6 +84,7 @@ def test_square_webhook_invalid_json(
self.assertTrue(response.status_code, 400)
self.assertIn(b"Unable to decode JSON", response.content)

@tag("square")
@override_settings(SQUARE_WEBHOOK_SIGNATURE_KEY=SIGNATURE_KEY)
@patch("square.utilities.webhooks_helper.is_valid_webhook_event_signature")
@patch("django.http.request.HttpRequest.build_absolute_uri")
Expand All @@ -100,6 +104,7 @@ def test_square_webhook_missing_event_id(
self.assertTrue(response.status_code, 400)
self.assertIn(b"Missing event_id", response.content)

@tag("square")
@override_settings(SQUARE_WEBHOOK_SIGNATURE_KEY=SIGNATURE_KEY)
@patch("django.http.request.HttpRequest.build_absolute_uri")
def test_square_webhook_idempotency(self, mock_build_absolute_uri):
Expand Down Expand Up @@ -246,6 +251,7 @@ def setUp(self) -> None:
self.order_item.save()
self.order.refresh_from_db()

@tag("square")
@override_settings(SQUARE_WEBHOOK_SIGNATURE_KEY=SIGNATURE_KEY)
@patch("django.http.request.HttpRequest.build_absolute_uri")
def test_dispute_webhook(self, mock_build_absolute_uri):
Expand Down Expand Up @@ -275,6 +281,7 @@ def test_dispute_webhook(self, mock_build_absolute_uri):
self.assertEqual(self.attendee.firstName, ban_list.firstName)
self.assertEqual(self.attendee.lastName, ban_list.lastName)

@tag("square")
@override_settings(SQUARE_WEBHOOK_SIGNATURE_KEY=SIGNATURE_KEY)
@patch("django.http.request.HttpRequest.build_absolute_uri")
def test_dispute_payment_id_not_found(self, mock_build_absolute_uri):
Expand Down
10 changes: 0 additions & 10 deletions registration/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,6 @@ def trigger_error(request):
registration.views.onsite_admin.no_sale,
name="no_sale",
),
url(
r"^onsite/signature/?$",
registration.views.onsite_admin.onsite_signature,
name="onsite_signature",
),
url(
r"^onsite/admin/signature/?$",
registration.views.onsite_admin.onsite_signature_prompt,
name="onsite_signature_prompt",
),
url(
r"^onsite/admin/discount/create/?$",
registration.views.onsite_admin.create_discount,
Expand Down
Loading

0 comments on commit 6c2123c

Please sign in to comment.