Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add signature capture to web form #323

Merged
merged 9 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/furthemore/apis:apis-base-11fb148
FROM ghcr.io/furthemore/apis:apis-base-35aadf5

LABEL org.opencontainers.image.source="https://github.com/furthemore/APIS"

Expand Down
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
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
2 changes: 1 addition & 1 deletion registration/views/cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def saveCart(cart):
)
attendee.save()

badge = Badge(badgeName=pda["badgeName"], event=event, attendee=attendee)
badge = Badge(badgeName=pda["badgeName"], event=event, attendee=attendee, signature_svg=pda.get("signature_svg"), signature_bitmap=pda.get("signature_bitmap"))
badge.save()

priceLevel = PriceLevel.objects.get(id=int(pdp["id"]))
Expand Down
Loading
Loading