12 Commits
1.10 ... 0.9

Author SHA1 Message Date
Sebastien Corbin
3f5d877b6a Release 0.9 2020-04-18 20:51:52 +02:00
Sebastien Corbin
cf2ad535e1 Add tests for admin usage 2020-04-18 20:48:06 +02:00
Sebastien Corbin
2be2c4dcc6 Update jSignature lib 2020-04-18 20:22:14 +02:00
Sebastien Corbin
d08e0ba276 Handle both admin and not admin usages 2020-04-18 20:22:04 +02:00
Sebastien Corbin
a6946d160d Include example project 2020-04-18 20:20:26 +02:00
Sebastien Corbin
ac8c20abb3 Remove dependency over six 2020-04-18 19:04:25 +02:00
Sebastien Corbin
622f3ea10b Rework test matrix and structure 2020-04-18 18:53:42 +02:00
Sébastien Corbin
c5f7c10d23 Merge pull request #12 from adremides/master
Fix jquery dependency for Django >2.2 version
2020-03-11 10:51:39 +01:00
Unknown
d0ae3a9c43 Fix jquery dependency for Django >2.2 version
Django changed the way media assets are merged, now using a topological sort algorithm. Dependencies are now needed to be added in the media so they are recognized by the algorithm.
2019-10-03 20:38:25 -03:00
Florent Lebreton
39f2ba9533 Merge pull request #11 from notanumber/master
Updated to fix an error when using jSignature field with Django 2
2019-07-03 16:55:36 +02:00
David Sauve
33b43b98b0 Added renderer=None to render call 2019-04-03 14:28:43 -07:00
David Sauve
9923614d6a Updated JSignatureField to remove SubfieldBase 2019-04-03 14:25:11 -07:00
33 changed files with 454 additions and 266 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
*.pyc
.coverage
htmlcov
*.egg-info
.tox
example.db

View File

@@ -1,32 +1,46 @@
language: python
python:
- 2.7
- 3.3
- 3.4
- 3.5
matrix:
fast_finish: true
allow_failures:
- env: DJANGO=master
include:
- python: 2.7
env: DJANGO=1.11
env:
- DJANGO_VERSION=1.7 MODULE=jsignature.tests
- DJANGO_VERSION=1.8 MODULE=jsignature.tests
- DJANGO_VERSION=1.9 MODULE=jsignature.tests
- DJANGO_VERSION=1.10 MODULE=jsignature.tests
- python: 3.5
env: DJANGO=1.11
- python: 3.5
env: DJANGO=2.2
- python: 3.6
env: DJANGO=1.11
- python: 3.6
env: DJANGO=2.2
- python: 3.6
env: DJANGO=3.0
- python: 3.6
env: DJANGO=master
- python: 3.7
env: DJANGO=2.2
- python: 3.7
env: DJANGO=3.0
- python: 3.7
env: DJANGO=master
- python: 3.8
env: DJANGO=2.2
- python: 3.8
env: DJANGO=3.0
- python: 3.8
env: DJANGO=master
install:
- pip install -r requirements.txt
- pip install -q Django==$DJANGO_VERSION
- pip install coverage
script: coverage run quicktest.py $MODULE
- pip install tox tox-travis
script:
- tox
after_success:
- pip install coveralls
- coveralls
# We need to exclude old versions of Python for tests with Django >= 1.9.
matrix:
exclude:
- python: 3.3
env: DJANGO_VERSION=1.9 MODULE=jsignature.tests
- python: 3.3
env: DJANGO_VERSION=1.10 MODULE=jsignature.tests
- pip install coveralls
- coveralls

10
CHANGES
View File

@@ -2,6 +2,16 @@
CHANGELOG
=========
0.9 (2020-04-18)
==================
** New **
- Add support for Django 2+
- Drop support for Django<1.11
- Add a ``JSIGNATURE_JQUERY`` settings to handle usage in admin in Django 2.1+
0.8 (2014-12-04)
==================

View File

@@ -6,12 +6,17 @@ It provides:
* A model field to store a captured signature;
* A mixin adding two fields (signature / signature_date) in any of your Django models.
.. image:: https://img.shields.io/pypi/v/django-jsignature.svg
:target: https://pypi.python.org/pypi/django-jsignature/
:alt: Latest PyPI version
.. image:: https://travis-ci.org/fle/django-jsignature.png?branch=master
:target: https://travis-ci.org/fle/django-jsignature
:target: https://travis-ci.org/fle/django-jsignature
:alt: Build status
.. image:: https://coveralls.io/repos/fle/django-jsignature/badge.png
:target: https://coveralls.io/r/fle/django-jsignature
:target: https://coveralls.io/r/fle/django-jsignature
:alt: Coverage status
.. image:: https://github.com/fle/django-jsignature/blob/master/screen.png
@@ -35,8 +40,8 @@ USAGE
# settings.py
INSTALLED_APPS = (
...
'jsignature',
...
'jsignature',
)
* Use provided form field and widget:
@@ -82,6 +87,13 @@ USAGE
# or as a file
signature_file_path = draw_signature(signature, as_file=True)
* By default, jSignature is made to work outside of admin, requiring that
you include the jQuery library in your ``<head>``.
If you want to use jSignature in the Django admin site, set the
``JSIGNATURE_JQUERY`` setting to ``admin``. Otherwise if set to any url
pointing to jQuery, it will be automatically included.
==================
CUSTOMIZATION
==================
@@ -121,7 +133,7 @@ Available settings are:
IN YOUR MODELS
==================
If you wan to store signatures, provided mixin gives a ``signature`` and a ``signature_date`` that update themselves:
If you want to store signatures, provided mixin gives a ``signature`` and a ``signature_date`` that update themselves:
::
@@ -132,11 +144,34 @@ If you wan to store signatures, provided mixin gives a ``signature`` and a ``sig
name = models.CharField()
==================
Example project
==================
If you want to have a demo of this package, just use the example project:
::
git clone https://github.com/fle/django-jsignature.git
cd django-jsignature
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install -e .
cd example_project
./manage.py migrate
./manage.py createsuperuser
Fill the user info, launch django with ``./manage.py runserver`` and head over to
`http://127.0.0.1:8000/ <http://127.0.0.1:8000/>`_ and login with the
credentials your provided.
==================
AUTHORS
==================
* Florent Lebreton <florent.lebreton@makina-corpus.com>
* Sébastien Corbin <sebastien.corbin@makina-corpus.com>
|makinacom|_

View File

5
example_project/admin.py Normal file
View File

@@ -0,0 +1,5 @@
from django.contrib import admin
from .models import ExampleModel
admin.site.register(ExampleModel)

10
example_project/manage.py Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

@@ -0,0 +1,26 @@
# Generated by Django 3.0.5 on 2020-04-18 12:18
from django.db import migrations, models
import jsignature.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ExampleModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('signature', jsignature.fields.JSignatureField(blank=True, null=True, verbose_name='Signature')),
('signature_date', models.DateTimeField(blank=True, null=True, verbose_name='Signature date')),
],
options={
'abstract': False,
},
),
]

View File

View File

@@ -0,0 +1,5 @@
from jsignature.mixins import JSignatureFieldsMixin
class ExampleModel(JSignatureFieldsMixin):
pass

View File

@@ -0,0 +1,67 @@
import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
DEBUG = True
ALLOWED_HOSTS = ['*']
MANAGERS = ADMINS = ()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'example.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
}
SECRET_KEY = 'notsosecret'
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
'context_processors': (
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages",
),
},
},
]
ROOT_URLCONF = 'example_project.urls'
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.admin',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.staticfiles',
'django.contrib.messages',
'jsignature',
'example_project',
)
STATIC_URL = '/static/'
JSIGNATURE_JQUERY = 'admin'

8
example_project/urls.py Normal file
View File

@@ -0,0 +1,8 @@
from __future__ import unicode_literals
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('', admin.site.urls),
]

View File

@@ -3,14 +3,21 @@
with jSignature jQuery plugin
"""
import json
import six
from django.db import models
from django.core.exceptions import ValidationError
from .forms import (
JSignatureField as JSignatureFormField,
JSIGNATURE_EMPTY_VALUES)
JSIGNATURE_EMPTY_VALUES,
)
try:
from django.utils import six
string_types = six.string_types
except ImportError:
string_types = str
class JSignatureField(models.Field):
@@ -23,6 +30,10 @@ class JSignatureField(models.Field):
return 'TextField'
def to_python(self, value):
"""
Validates that the input can be red as a JSON object. Returns a Python
datetime.date object.
"""
if value in JSIGNATURE_EMPTY_VALUES:
return None
elif isinstance(value, list):
@@ -32,18 +43,10 @@ class JSignatureField(models.Field):
except ValueError:
raise ValidationError('Invalid JSON format.')
def from_db_value(self, value, expression, connection, context):
if value in JSIGNATURE_EMPTY_VALUES:
return None
try:
return json.loads(value)
except ValueError:
raise ValidationError('Invalid JSON format.')
def get_prep_value(self, value):
if value in JSIGNATURE_EMPTY_VALUES:
return None
elif isinstance(value, six.string_types):
elif isinstance(value, string_types):
return value
elif isinstance(value, list):
return json.dumps(value)
@@ -53,4 +56,3 @@ class JSignatureField(models.Field):
defaults = {'form_class': JSignatureFormField}
defaults.update(kwargs)
return super(JSignatureField, self).formfield(**defaults)

View File

@@ -2,6 +2,7 @@
A django mixin providing fields to store a signature captured
with jSignature jQuery plugin
"""
import json
from datetime import datetime
from django.db import models
from django.utils.translation import ugettext_lazy as _
@@ -28,7 +29,7 @@ class JSignatureFieldsMixin(models.Model):
original = not is_new and self.__class__.objects.get(pk=self.pk)
if self.signature:
if is_new or self.signature != original.signature:
if is_new or json.dumps(self.signature) != original.signature:
self.signature_date = datetime.now()
else:
self.signature_date = None

View File

@@ -17,6 +17,9 @@ JSIGNATURE_UNDO_BUTTON = getattr(
JSIGNATURE_RESET_BUTTON = getattr(
settings, 'JSIGNATURE_RESET_BUTTON', True)
JSIGNATURE_JQUERY = getattr(
settings, 'JSIGNATURE_JQUERY', 'custom')
JSIGNATURE_DEFAULT_CONFIG = {
'width': JSIGNATURE_WIDTH,
'height': JSIGNATURE_HEIGHT,

View File

@@ -1,21 +1,22 @@
$(document).ready(function() {
$(".jsign-container").each(function(){
var config = $(this).data('config');
var value = $(this).data('initial-value');
$(this).jSignature(config);
$(this).jSignature("setData", value, "native");
});
(function($) {
$(document).ready(function() {
$(".jsign-container").each(function(){
var config = $(this).data('config');
var value = $(this).data('initial-value');
$(this).jSignature(config);
$(this).jSignature("setData", value, "native");
});
/* Each time user is done drawing a stroke, update value of hidden input */
$(".jsign-container").on("change", function(e) {
var jSignature_data = $(this).jSignature('getData', 'native');
var django_field_name = $(this).attr('id').split(/_(.+)/)[1];
$('#id_' + django_field_name).val(JSON.stringify(jSignature_data));
});
/* Each time user is done drawing a stroke, update value of hidden input */
$(".jsign-container").on("change", function(e) {
var jSignature_data = $(this).jSignature('getData', 'native');
var django_field_name = $(this).attr('id').split(/_(.+)/)[1];
$('#id_' + django_field_name).val(JSON.stringify(jSignature_data));
});
/* Bind clear button */
$(".jsign-wrapper input").on("click", function(e) {
$(this).siblings('.jsign-container').jSignature('reset');
/* Bind clear button */
$(".jsign-wrapper input").on("click", function(e) {
$(this).siblings('.jsign-container').jSignature('reset');
});
});
});
})(jQuery || django.jQuery)

104
jsignature/static/js/jSignature.min.js vendored Normal file → Executable file
View File

@@ -1,9 +1,14 @@
/*
jSignature v2 "2013-08-26T07:00" "commit ID 986265299fecdb0f5b20acce92319220809707bf"
jSignature v2 "2012-11-01T22:48" "commit ID 1c15dfafecc75925c3b7d529356a558b59220edb"
Copyright (c) 2012 Willow Systems Corp http://willow-systems.com
Copyright (c) 2010 Brinley Ang http://www.unbolt.net
MIT License <http://www.opensource.org/licenses/mit-license.php>
MIT License <http://www.opensource.org/licenses/mit-license.php>
Simplify.js BSD
(c) 2012, Vladimir Agafonkin
mourner.github.com/simplify-js
base64 encoder
@@ -26,54 +31,47 @@ jSignature v2 jSignature's custom "base30" format export and import plugins.
jSignature v2 SVG export plugin.
Simplify.js BSD
(c) 2012, Vladimir Agafonkin
mourner.github.com/simplify-js
*/
(function(){function r(b){var a,e=b.css("color"),d;b=b[0];for(var q=!1;b&&!d&&!q;){try{a=$(b).css("background-color")}catch(c){a="transparent"}"transparent"!==a&&"rgba(0, 0, 0, 0)"!==a&&(d=a);q=b.body;b=b.parentNode}b=/rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/;var q=/#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/,k;a=void 0;(a=e.match(b))?k={r:parseInt(a[1],10),g:parseInt(a[2],10),b:parseInt(a[3],10)}:(a=e.match(q))&&(k={r:parseInt(a[1],16),g:parseInt(a[2],16),b:parseInt(a[3],16)});var n;
d?(a=void 0,(a=d.match(b))?n={r:parseInt(a[1],10),g:parseInt(a[2],10),b:parseInt(a[3],10)}:(a=d.match(q))&&(n={r:parseInt(a[1],16),g:parseInt(a[2],16),b:parseInt(a[3],16)})):n=k?127<Math.max.apply(null,[k.r,k.g,k.b])?{r:0,g:0,b:0}:{r:255,g:255,b:255}:{r:255,g:255,b:255};a=function(a){return"rgb("+[a.r,a.g,a.b].join(", ")+")"};k&&n?(b=Math.max.apply(null,[k.r,k.g,k.b]),k=Math.max.apply(null,[n.r,n.g,n.b]),k=Math.round(k+-0.75*(k-b)),k={r:k,g:k,b:k}):k?(k=Math.max.apply(null,[k.r,k.g,k.b]),b=1,127<
k&&(b=-1),k=Math.round(k+96*b),k={r:k,g:k,b:k}):k={r:191,g:191,b:191};return{color:e,"background-color":n?a(n):d,"decor-color":a(k)}}function l(b,a){this.x=b;this.y=a;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var e=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length=
0;else if(0===this.x)this._length=a,this.y=a*e(this.y);else if(0===this.y)this._length=a,this.x=a*e(this.x);else{var b=Math.abs(this.y/this.x),c=Math.sqrt(Math.pow(a,2)/(1+Math.pow(b,2))),b=b*c;this._length=a;this.x=c*e(this.x);this.y=b*e(this.y)}return this};this.angleTo=function(a){var b=this.getLength()*a.getLength();return 0===b?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/b,-1),1))/Math.PI}}function h(b,a){this.x=b;this.y=a;this.getVectorToCoordinates=function(a,b){return new l(a-this.x,
b-this.y)};this.getVectorFromCoordinates=function(a,b){return this.getVectorToCoordinates(a,b).reverse()};this.getVectorToPoint=function(a){return new l(a.x-this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function p(b,a,e,d,c){this.data=b;this.context=a;if(b.length)for(var E=b.length,k,n,f=0;f<E;f++){k=b[f];n=k.x.length;e.call(a,k);for(var D=1;D<n;D++)d.call(a,k,D);c.call(a,k)}this.changed=function(){};this.startStrokeFn=e;this.addToStrokeFn=d;this.endStrokeFn=
c;this.inStroke=!1;this._stroke=this._lastPoint=null;this.startStroke=function(a){if(a&&"number"==typeof a.x&&"number"==typeof a.y){this._stroke={x:[a.x],y:[a.y]};this.data.push(this._stroke);this._lastPoint=a;this.inStroke=!0;var b=this._stroke,e=this.startStrokeFn,d=this.context;setTimeout(function(){e.call(d,b)},3);return a}return null};this.addToStroke=function(a){if(this.inStroke&&"number"===typeof a.x&&"number"===typeof a.y&&4<Math.abs(a.x-this._lastPoint.x)+Math.abs(a.y-this._lastPoint.y)){var b=
this._stroke.x.length;this._stroke.x.push(a.x);this._stroke.y.push(a.y);this._lastPoint=a;var e=this._stroke,d=this.addToStrokeFn,c=this.context;setTimeout(function(){d.call(c,e,b)},3);return a}return null};this.endStroke=function(){var a=this.inStroke;this.inStroke=!1;this._lastPoint=null;if(a){var b=this._stroke,e=this.endStrokeFn,d=this.context,c=this.changed;setTimeout(function(){e.call(d,b);c.call(d)},3);return!0}return null}}function m(b,a,e,d){if("ratio"===a||"%"===a.split("")[a.length-1])this.eventTokens[e+
".parentresized"]=d.subscribe(e+".parentresized",function(a,c,k,n){return function(){var n=c.width();if(n!==k){for(var f in a)a.hasOwnProperty(f)&&(d.unsubscribe(a[f]),delete a[f]);var z=b.settings;b.$parent.children().remove();for(f in b)b.hasOwnProperty(f)&&delete b[f];f=z.data;var n=1*n/k,t=[],g,h,l,m,p,r;h=0;for(l=f.length;h<l;h++){r=f[h];g={x:[],y:[]};m=0;for(p=r.x.length;m<p;m++)g.x.push(r.x[m]*n),g.y.push(r.y[m]*n);t.push(g)}z.data=t;c[e](z)}}}(this.eventTokens,this.$parent,this.$parent.width(),
1*this.canvas.width/this.canvas.height))}function B(b,a,e){var d=this.$parent=$(b);b=this.eventTokens={};this.events=new w(this);var c=$.fn[g]("globalEvents"),f={width:"ratio",height:"ratio",sizeRatio:4,color:"#000","background-color":"#fff","decor-color":"#eee",lineWidth:0,minFatFingerCompensation:-10,showUndoButton:!1,readOnly:!1,data:[]};$.extend(f,r(d));a&&$.extend(f,a);this.settings=f;for(var k in e)e.hasOwnProperty(k)&&e[k].call(this,k);this.events.publish(g+".initializing");this.$controlbarUpper=
$('<div style="padding:0 !important; margin:0 !important;width: 100% !important; height: 0 !important; -ms-touch-action: none;margin-top:-1em !important; margin-bottom:1em !important;"></div>').appendTo(d);this.isCanvasEmulator=!1;a=this.canvas=this.initializeCanvas(f);e=$(a);this.$controlbarLower=$('<div style="padding:0 !important; margin:0 !important;width: 100% !important; height: 0 !important; -ms-touch-action: none;margin-top:-1.5em !important; margin-bottom:1.5em !important; position: relative;"></div>').appendTo(d);
this.canvasContext=a.getContext("2d");e.data(g+".this",this);f.lineWidth=f.lineWidth?f.lineWidth:Math.max(Math.round(a.width/400),2);this.lineCurveThreshold=3*f.lineWidth;f.cssclass&&""!=$.trim(f.cssclass)&&e.addClass(f.cssclass);this.fatFingerCompensation=0;d=function(a){var b,e,d=function(d){d=d.changedTouches&&0<d.changedTouches.length?d.changedTouches[0]:d;return new h(Math.round(d.pageX+b),Math.round(d.pageY+e)+a.fatFingerCompensation)},c=new v(750,function(){a.dataEngine.endStroke()});this.drawEndHandler=
function(b){if(!a.settings.readOnly){try{b.preventDefault()}catch(e){}c.clear();a.dataEngine.endStroke()}};this.drawStartHandler=function(f){if(!a.settings.readOnly){f.preventDefault();var k=$(a.canvas).offset();b=-1*k.left;e=-1*k.top;a.dataEngine.startStroke(d(f));c.kick()}};this.drawMoveHandler=function(b){a.settings.readOnly||(b.preventDefault(),a.dataEngine.inStroke&&(a.dataEngine.addToStroke(d(b)),c.kick()))};return this}.call({},this);(function(a,b,e){var d=this.canvas,c=$(d);this.isCanvasEmulator?
(c.bind("mousemove."+g,e),c.bind("mouseup."+g,a),c.bind("mousedown."+g,b)):(d.ontouchstart=function(c){d.onmousedown=d.onmouseup=d.onmousemove=void 0;this.fatFingerCompensation=f.minFatFingerCompensation&&-3*f.lineWidth>f.minFatFingerCompensation?-3*f.lineWidth:f.minFatFingerCompensation;b(c);d.ontouchend=a;d.ontouchstart=b;d.ontouchmove=e},d.onmousedown=function(c){d.ontouchstart=d.ontouchend=d.ontouchmove=void 0;b(c);d.onmousedown=b;d.onmouseup=a;d.onmousemove=e},window.navigator.msPointerEnabled&&
(d.onmspointerdown=b,d.onmspointerup=a,d.onmspointermove=e))}).call(this,d.drawEndHandler,d.drawStartHandler,d.drawMoveHandler);b[g+".windowmouseup"]=c.subscribe(g+".windowmouseup",d.drawEndHandler);this.events.publish(g+".attachingEventHandlers");m.call(this,this,f.width.toString(10),g,c);this.resetCanvas(f.data);this.events.publish(g+".initialized");return this}function u(b){if(b.getContext)return!1;var a=b.ownerDocument.parentWindow,e=a.FlashCanvas?b.ownerDocument.parentWindow.FlashCanvas:"undefined"===
typeof FlashCanvas?void 0:FlashCanvas;if(e){b=e.initElement(b);e=1;a&&(a.screen&&a.screen.deviceXDPI&&a.screen.logicalXDPI)&&(e=1*a.screen.deviceXDPI/a.screen.logicalXDPI);if(1!==e)try{$(b).children("object").get(0).resize(Math.ceil(b.width*e),Math.ceil(b.height*e)),b.getContext("2d").scale(e,e)}catch(d){}return!0}throw Error("Canvas element does not support 2d context. jSignature cannot proceed.");}var g="jSignature",v=function(b,a){var e;this.kick=function(){clearTimeout(e);e=setTimeout(a,b)};this.clear=
function(){clearTimeout(e)};return this},w=function(b){this.topics={};this.context=b?b:this;this.publish=function(a,b,d,c){if(this.topics[a]){var f=this.topics[a],k=Array.prototype.slice.call(arguments,1),n=[],g,h,z,t;h=0;for(z=f.length;h<z;h++)t=f[h],g=t[0],t[1]&&(t[0]=function(){},n.push(h)),g.apply(this.context,k);h=0;for(z=n.length;h<z;h++)f.splice(n[h],1)}};this.subscribe=function(a,b,d){this.topics[a]?this.topics[a].push([b,d]):this.topics[a]=[[b,d]];return{topic:a,callback:b}};this.unsubscribe=
function(a){if(this.topics[a.topic])for(var b=this.topics[a.topic],d=0,c=b.length;d<c;d++)b[d]&&b[d][0]===a.callback&&b.splice(d,1)}},x=function(b,a,e,d,c){b.beginPath();b.moveTo(a,e);b.lineTo(d,c);b.stroke()},y=function(b){var a=this.canvasContext,e=b.x[0];b=b.y[0];var d=this.settings.lineWidth,c=a.fillStyle;a.fillStyle=a.strokeStyle;a.fillRect(e+d/-2,b+d/-2,d,d);a.fillStyle=c},c=function(b,a){var e=new h(b.x[a-1],b.y[a-1]),d=new h(b.x[a],b.y[a]),c=e.getVectorToPoint(d);if(1<a){var f=new h(b.x[a-
2],b.y[a-2]),k=f.getVectorToPoint(e),g;if(k.getLength()>this.lineCurveThreshold){g=2<a?(new h(b.x[a-3],b.y[a-3])).getVectorToPoint(f):new l(0,0);var s=0.35*k.getLength(),m=k.angleTo(g.reverse()),z=c.angleTo(k.reverse());g=(new l(g.x+k.x,g.y+k.y)).resizeTo(Math.max(0.05,m)*s);var t=(new l(k.x+c.x,k.y+c.y)).reverse().resizeTo(Math.max(0.05,z)*s),k=this.canvasContext,s=f.x,z=f.y,m=e.x,F=e.y,p=f.x+g.x,f=f.y+g.y;g=e.x+t.x;t=e.y+t.y;k.beginPath();k.moveTo(s,z);k.bezierCurveTo(p,f,g,t,m,F);k.stroke()}}c.getLength()<=
this.lineCurveThreshold&&x(this.canvasContext,e.x,e.y,d.x,d.y)},f=function(b){var a=b.x.length-1;if(0<a){var e=new h(b.x[a],b.y[a]),d=new h(b.x[a-1],b.y[a-1]),c=d.getVectorToPoint(e);if(c.getLength()>this.lineCurveThreshold)if(1<a){b=(new h(b.x[a-2],b.y[a-2])).getVectorToPoint(d);var f=(new l(b.x+c.x,b.y+c.y)).resizeTo(c.getLength()/2),c=this.canvasContext;b=d.x;var a=d.y,k=e.x,g=e.y,s=d.x+f.x,d=d.y+f.y,f=e.x,e=e.y;c.beginPath();c.moveTo(b,a);c.bezierCurveTo(s,d,f,e,k,g);c.stroke()}else x(this.canvasContext,
d.x,d.y,e.x,e.y)}};B.prototype.resetCanvas=function(b,a){var e=this.canvas,d=this.settings,q=this.canvasContext,h=this.isCanvasEmulator,k=e.width,n=e.height;a||q.clearRect(0,0,k+30,n+30);q.shadowColor=q.fillStyle=d["background-color"];h&&q.fillRect(0,0,k+30,n+30);q.lineWidth=Math.ceil(parseInt(d.lineWidth,10));q.lineCap=q.lineJoin="round";if(null!=d["decor-color"]){q.strokeStyle=d["decor-color"];q.shadowOffsetX=0;q.shadowOffsetY=0;var s=Math.round(n/5);x(q,1.5*s,n-s,k-1.5*s,n-s)}q.strokeStyle=d.color;
h||(q.shadowColor=q.strokeStyle,q.shadowOffsetX=0.5*q.lineWidth,q.shadowOffsetY=-0.6*q.lineWidth,q.shadowBlur=0);b||(b=[]);q=this.dataEngine=new p(b,this,y,c,f);d.data=b;$(e).data(g+".data",b).data(g+".settings",d);q.changed=function(a,b,d){return function(){b.publish(d+".change");a.trigger("change")}}(this.$parent,this.events,g);q.changed();return!0};B.prototype.initializeCanvas=function(b){var a=document.createElement("canvas"),e=$(a);b.width===b.height&&"ratio"===b.height&&(b.width="100%");e.css("margin",
0).css("padding",0).css("border","none").css("height","ratio"!==b.height&&b.height?b.height.toString(10):1).css("width","ratio"!==b.width&&b.width?b.width.toString(10):1).css("-ms-touch-action","none");e.appendTo(this.$parent);"ratio"===b.height?e.css("height",Math.round(e.width()/b.sizeRatio)):"ratio"===b.width&&e.css("width",Math.round(e.height()*b.sizeRatio));e.addClass(g);a.width=e.width();a.height=e.height();this.isCanvasEmulator=u(a);a.onselectstart=function(a){a&&a.preventDefault&&a.preventDefault();
a&&a.stopPropagation&&a.stopPropagation();return!1};return a};(function(b){function a(a,b,d){var c=new Image,e=this;c.onload=function(){e.getContext("2d").drawImage(c,0,0,c.width<e.width?c.width:e.width,c.height<e.height?c.height:e.height)};c.src="data:"+b+","+a}function e(a,b){this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this").resetCanvas(a,b);return this}function d(a,b){if(void 0===b&&("string"===typeof a&&"data:"===a.substr(0,5))&&(b=a.slice(5).split(",")[0],a=a.slice(6+b.length),
b===a))return;var c=this.find("canvas."+g).add(this.filter("canvas."+g));if(h.hasOwnProperty(b))0!==c.length&&h[b].call(c[0],a,b,function(a){return function(){return a.resetCanvas.apply(a,arguments)}}(c.data(g+".this")));else throw Error(g+" is unable to find import plugin with for format '"+String(b)+"'");return this}var c=new w;(function(a,b,c,e){var d,f=function(){a.publish(b+".parentresized")};c(e).bind("resize."+b,function(){d&&clearTimeout(d);d=setTimeout(f,500)}).bind("mouseup."+b,function(c){a.publish(b+
".windowmouseup")})})(c,g,$,b);var f={},k={"default":function(a){return this.toDataURL()},"native":function(a){return a},image:function(a){a=this.toDataURL();if("string"===typeof a&&4<a.length&&"data:"===a.slice(0,5)&&-1!==a.indexOf(",")){var b=a.indexOf(",");return[a.slice(5,b),a.substr(b+1)]}return[]}},h={"native":function(a,b,c){c(a)},image:a,"image/png;base64":a,"image/jpeg;base64":a,"image/jpg;base64":a},s={"export":k,"import":h,instance:f},m={init:function(a){return this.each(function(){var b,
c=!1;for(b=this.parentNode;b&&!c;)c=b.body,b=b.parentNode;c&&new B(this,a,f)})},getSettings:function(){return this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this").settings},isModified:function(){return null!==this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this").dataEngine._stroke},updateSetting:function(a,b,c){var e=this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this");e.settings[a]=b;e.resetCanvas(c?null:e.settings.data,!0);return e.settings[a]},clear:e,
reset:e,addPlugin:function(a,b,c){s.hasOwnProperty(a)&&(s[a][b]=c);return this},listPlugins:function(a){var b=[];if(s.hasOwnProperty(a)){a=s[a];for(var c in a)a.hasOwnProperty(c)&&b.push(c)}return b},getData:function(a){var b=this.find("canvas."+g).add(this.filter("canvas."+g));void 0===a&&(a="default");if(0!==b.length&&k.hasOwnProperty(a))return k[a].call(b.get(0),b.data(g+".data"))},importData:d,setData:d,globalEvents:function(){return c},disable:function(){this.find("input").attr("disabled",1);
this.find("canvas."+g).addClass("disabled").data(g+".this").settings.readOnly=!0},enable:function(){this.find("input").removeAttr("disabled");this.find("canvas."+g).removeClass("disabled").data(g+".this").settings.readOnly=!1},events:function(){return this.find("canvas."+g).add(this.filter("canvas."+g)).data(g+".this").events}};$.fn[g]=function(a){if(a&&"object"!==typeof a){if("string"===typeof a&&m[a])return m[a].apply(this,Array.prototype.slice.call(arguments,1));$.error("Method "+String(a)+" does not exist on jQuery."+
g)}else return m.init.apply(this,arguments)}})(window)})();
(function(){function r(l,h,p){l=l.call(this);(function(h,l,p){h.events.subscribe(p+".change",function(){h.dataEngine.data.length?l.show():l.hide()})})(this,l,h);(function(h,l,p){var g=p+".undo";l.bind("click",function(){h.events.publish(g)});h.events.subscribe(g,function(){var g=h.dataEngine.data;g.length&&(g.pop(),h.resetCanvas(g))})})(this,l,this.events.topics.hasOwnProperty(h+".undo")?p:h)}$.fn.jSignature("addPlugin","instance","UndoButton",function(l){this.events.subscribe("jSignature.attachingEventHandlers",
function(){if(this.settings[l]){var h=this.settings[l];"function"!==typeof h&&(h=function(){var h=$('<input type="button" value="Undo last stroke" style="position:absolute;display:none;margin:0 !important;top:auto" />').appendTo(this.$controlbarLower),l=h.width();h.css("left",Math.round((this.canvas.width-l)/2));l!==h.width()&&h.width(l);return h});r.call(this,h,"jSignature",l)}})})})();
(function(){for(var r={},l={},h="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX".split(""),p=h.length/2,m=p-1;-1<m;m--)r[h[m]]=h[m+p],l[h[m+p]]=h[m];var B=function(c){c=c.split("");for(var f=c.length,b=1;b<f;b++)c[b]=r[c[b]];return c.join("")},u=function(c){for(var f=[],b=0,a=1,e=c.length,d,g,h=0;h<e;h++)d=Math.round(c[h]),g=d-b,b=d,0>g&&0<a?(a=-1,f.push("Z")):0<g&&0>a&&(a=1,f.push("Y")),d=Math.abs(g),d>=p?f.push(B(d.toString(p))):f.push(d.toString(p));return f.join("")},g=function(c){var f=
[];c=c.split("");for(var b=c.length,a,e=1,d=[],g=0,h=0;h<b;h++)a=c[h],a in r||"Z"===a||"Y"===a?(0!==d.length&&(d=parseInt(d.join(""),p)*e+g,f.push(d),g=d),"Z"===a?(e=-1,d=[]):"Y"===a?(e=1,d=[]):d=[a]):d.push(l[a]);f.push(parseInt(d.join(""),p)*e+g);return f},v=function(c){for(var f=[],b=c.length,a,e=0;e<b;e++)a=c[e],f.push(u(a.x)),f.push(u(a.y));return f.join("_")},w=function(c){var f=[];c=c.split("_");for(var b=c.length/2,a=0;a<b;a++)f.push({x:g(c[2*a]),y:g(c[2*a+1])});return f},x=function(c){return["image/jsignature;base30",
v(c)]},y=function(c,f,b){"string"===typeof c&&("image/jsignature;base30"===c.substring(0,23).toLowerCase()&&(c=c.substring(24)),b(w(c)))};if(null==this.jQuery)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");(function(c){c=c.fn.jSignature;c("addPlugin","export","base30",x);c("addPlugin","export","image/jsignature;base30",x);c("addPlugin","import","base30",y);c("addPlugin","import","image/jsignature;base30",y)})(this.jQuery);this.jSignatureDebug&&
(this.jSignatureDebug.base30={remapTailChars:B,compressstrokeleg:u,uncompressstrokeleg:g,compressstrokes:v,uncompressstrokes:w,charmap:r})}).call("undefined"!==typeof window?window:this);
(function(){function r(c,f){this.x=c;this.y=f;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var b=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length=0;else if(0===this.x)this._length=a,this.y=a*b(this.y);else if(0===this.y)this._length=a,this.x=a*b(this.x);else{var c=Math.abs(this.y/
this.x),d=Math.sqrt(Math.pow(a,2)/(1+Math.pow(c,2))),c=c*d;this._length=a;this.x=d*b(this.x);this.y=c*b(this.y)}return this};this.angleTo=function(a){var b=this.getLength()*a.getLength();return 0===b?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/b,-1),1))/Math.PI}}function l(c,f){this.x=c;this.y=f;this.getVectorToCoordinates=function(b,a){return new r(b-this.x,a-this.y)};this.getVectorFromCoordinates=function(b,a){return this.getVectorToCoordinates(b,a).reverse()};this.getVectorToPoint=function(b){return new r(b.x-
this.x,b.y-this.y)};this.getVectorFromPoint=function(b){return this.getVectorToPoint(b).reverse()}}function h(c,f){var b=Math.pow(10,f);return Math.round(c*b)/b}function p(c,f,b){f+=1;var a=new l(c.x[f-1],c.y[f-1]),e=new l(c.x[f],c.y[f]),e=a.getVectorToPoint(e),d=new l(c.x[f-2],c.y[f-2]),a=d.getVectorToPoint(a);return a.getLength()>b?(b=2<f?(new l(c.x[f-3],c.y[f-3])).getVectorToPoint(d):new r(0,0),c=0.35*a.getLength(),d=a.angleTo(b.reverse()),f=e.angleTo(a.reverse()),b=(new r(b.x+a.x,b.y+a.y)).resizeTo(Math.max(0.05,
d)*c),e=(new r(a.x+e.x,a.y+e.y)).reverse().resizeTo(Math.max(0.05,f)*c),e=new r(a.x+e.x,a.y+e.y),["c",h(b.x,2),h(b.y,2),h(e.x,2),h(e.y,2),h(a.x,2),h(a.y,2)]):["l",h(a.x,2),h(a.y,2)]}function m(c,f){var b=c.x.length-1,a=new l(c.x[b],c.y[b]),e=new l(c.x[b-1],c.y[b-1]),a=e.getVectorToPoint(a);if(1<b&&a.getLength()>f){var b=(new l(c.x[b-2],c.y[b-2])).getVectorToPoint(e),e=a.angleTo(b.reverse()),d=0.35*a.getLength(),b=(new r(b.x+a.x,b.y+a.y)).resizeTo(Math.max(0.05,e)*d);return["c",h(b.x,2),h(b.y,2),h(a.x,
2),h(a.y,2),h(a.x,2),h(a.y,2)]}return["l",h(a.x,2),h(a.y,2)]}function B(c,f,b){f=["M",h(c.x[0]-f,2),h(c.y[0]-b,2)];b=1;for(var a=c.x.length-1;b<a;b++)f.push.apply(f,p(c,b,1));0<a?f.push.apply(f,m(c,b,1)):0===a&&f.push.apply(f,["l",1,1]);return f.join(" ")}function u(c){var f=['<?xml version="1.0" encoding="UTF-8" standalone="no"?>','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'],b,a=c.length,e,d=[],g=[],h=e=b=0,k=0,n=[];if(0!==a){for(b=0;b<a;b++){h=
c[b];k=[];e={x:[],y:[]};for(var l=void 0,m=void 0,l=0,m=h.x.length;l<m;l++)k.push({x:h.x[l],y:h.y[l]});k=simplify(k,0.7,!0);l=0;for(m=k.length;l<m;l++)e.x.push(k[l].x),e.y.push(k[l].y);n.push(e);d=d.concat(e.x);g=g.concat(e.y)}c=Math.min.apply(null,d)-1;a=Math.max.apply(null,d)+1;d=Math.min.apply(null,g)-1;g=Math.max.apply(null,g)+1;h=0>c?0:c;k=0>d?0:d;b=a-c;e=g-d}f.push('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+b.toString()+'" height="'+e.toString()+'">');b=0;for(a=n.length;b<
a;b++)e=n[b],f.push('<path fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="'+B(e,h,k)+'"/>');f.push("</svg>");return f.join("")}function g(c){return[x,u(c)]}function v(c){return[y,w(u(c))]}(function(c,f){"use strict";(typeof exports!=f+""?exports:c).simplify=function(b,a,c){a=a!==f?a*a:1;if(!c){var d=b.length,g,h=b[0],k=[h];for(c=1;c<d;c++){g=b[c];var l=g.x-h.x,m=g.y-h.y;l*l+m*m>a&&(k.push(g),h=g)}b=(h!==g&&k.push(g),k)}g=b;c=g.length;var d=new (typeof Uint8Array!=
f+""?Uint8Array:Array)(c),h=0,k=c-1,p,r,t=[],x=[],B=[];for(d[h]=d[k]=1;k;){m=0;for(l=h+1;l<k;l++){p=g[l];var A=g[h],w=g[k],u=A.x,v=A.y,A=w.x-u,C=w.y-v,y=void 0;if(0!==A||0!==C)y=((p.x-u)*A+(p.y-v)*C)/(A*A+C*C),1<y?(u=w.x,v=w.y):0<y&&(u+=A*y,v+=C*y);p=(A=p.x-u,C=p.y-v,A*A+C*C);p>m&&(r=l,m=p)}m>a&&(d[r]=1,t.push(h),x.push(r),t.push(r),x.push(k));h=t.pop();k=x.pop()}for(l=0;l<c;l++)d[l]&&B.push(g[l]);return b=B,b}})(window);if("function"!==typeof w)var w=function(c){var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""),
b,a,e,d,g=0,h=0,k="",k=[];do b=c.charCodeAt(g++),a=c.charCodeAt(g++),e=c.charCodeAt(g++),d=b<<16|a<<8|e,b=d>>18&63,a=d>>12&63,e=d>>6&63,d&=63,k[h++]=f[b]+f[a]+f[e]+f[d];while(g<c.length);k=k.join("");c=c.length%3;return(c?k.slice(0,c-3):k)+"===".slice(c||3)};var x="image/svg+xml",y="image/svg+xml;base64";if("undefined"===typeof $)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");(function(c){c=c.fn.jSignature;c("addPlugin","export","svg",
g);c("addPlugin","export",x,g);c("addPlugin","export","svgbase64",v);c("addPlugin","export",y,v)})($)})();
(function(){function q(b){for(var j,s=b.css("color"),g,b=b[0],c=!1;b&&!g&&!c;){try{j=$(b).css("background-color")}catch(d){j="transparent"}"transparent"!==j&&"rgba(0, 0, 0, 0)"!==j&&(g=j);c=b.body;b=b.parentNode}var b=/rgb[a]*\((\d+),\s*(\d+),\s*(\d+)/,c=/#([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})([AaBbCcDdEeFf\d]{2})/,a;j=void 0;(j=s.match(b))?a={r:parseInt(j[1],10),g:parseInt(j[2],10),b:parseInt(j[3],10)}:(j=s.match(c))&&(a={r:parseInt(j[1],16),g:parseInt(j[2],16),b:parseInt(j[3],16)});var f;g?
(j=void 0,(j=g.match(b))?f={r:parseInt(j[1],10),g:parseInt(j[2],10),b:parseInt(j[3],10)}:(j=g.match(c))&&(f={r:parseInt(j[1],16),g:parseInt(j[2],16),b:parseInt(j[3],16)})):f=a?127<Math.max.apply(null,[a.r,a.g,a.b])?{r:0,g:0,b:0}:{r:255,g:255,b:255}:{r:255,g:255,b:255};j=function(b){return"rgb("+[b.r,b.g,b.b].join(", ")+")"};a&&f?(b=Math.max.apply(null,[a.r,a.g,a.b]),a=Math.max.apply(null,[f.r,f.g,f.b]),a=Math.round(a+-0.75*(a-b)),a={r:a,g:a,b:a}):a?(a=Math.max.apply(null,[a.r,a.g,a.b]),b=1,127<a&&
(b=-1),a=Math.round(a+96*b),a={r:a,g:a,b:a}):a={r:191,g:191,b:191};return{color:s,"background-color":f?j(f):g,"decor-color":j(a)}}function m(b,j){this.x=b;this.y=j;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var s=function(b){return Math.round(b/Math.abs(b))};this.resizeTo=function(b){if(0===this.x&&0===this.y)this._length=0;else if(0===
this.x)this._length=b,this.y=b*s(this.y);else if(0===this.y)this._length=b,this.x=b*s(this.x);else{var j=Math.abs(this.y/this.x),a=Math.sqrt(Math.pow(b,2)/(1+Math.pow(j,2))),j=j*a;this._length=b;this.x=a*s(this.x);this.y=j*s(this.y)}return this};this.angleTo=function(b){var j=this.getLength()*b.getLength();return 0===j?0:Math.acos(Math.min(Math.max((this.x*b.x+this.y*b.y)/j,-1),1))/Math.PI}}function k(b,j){this.x=b;this.y=j;this.getVectorToCoordinates=function(b,j){return new m(b-this.x,j-this.y)};
this.getVectorFromCoordinates=function(b,j){return this.getVectorToCoordinates(b,j).reverse()};this.getVectorToPoint=function(b){return new m(b.x-this.x,b.y-this.y)};this.getVectorFromPoint=function(b){return this.getVectorToPoint(b).reverse()}}function t(b,j,a,g,c){this.data=b;this.context=j;if(b.length)for(var d=b.length,f,l,i=0;i<d;i++){f=b[i];l=f.x.length;a.call(j,f);for(var e=1;e<l;e++)g.call(j,f,e);c.call(j,f)}this.changed=function(){};this.startStrokeFn=a;this.addToStrokeFn=g;this.endStrokeFn=
c;this.inStroke=!1;this._stroke=this._lastPoint=null;this.startStroke=function(b){if(b&&"number"==typeof b.x&&"number"==typeof b.y){this._stroke={x:[b.x],y:[b.y]};this.data.push(this._stroke);this._lastPoint=b;this.inStroke=!0;var j=this._stroke,a=this.startStrokeFn,g=this.context;setTimeout(function(){a.call(g,j)},3);return b}return null};this.addToStroke=function(b){if(this.inStroke&&"number"===typeof b.x&&"number"===typeof b.y&&4<Math.abs(b.x-this._lastPoint.x)+Math.abs(b.y-this._lastPoint.y)){var j=
this._stroke.x.length;this._stroke.x.push(b.x);this._stroke.y.push(b.y);this._lastPoint=b;var a=this._stroke,g=this.addToStrokeFn,s=this.context;setTimeout(function(){g.call(s,a,j)},3);return b}return null};this.endStroke=function(){var b=this.inStroke;this.inStroke=!1;this._lastPoint=null;if(b){var j=this._stroke,a=this.endStrokeFn,g=this.context,s=this.changed;setTimeout(function(){a.call(g,j);s.call(g)},3);return!0}return null}}function r(b,j,a){var g=this.$parent=$(b),b=this.eventTokens={};this.events=
new v(this);var c=$.fn[e]("globalEvents"),d={width:"ratio",height:"ratio",sizeRatio:4,color:"#000","background-color":"#fff","decor-color":"#eee",lineWidth:0,minFatFingerCompensation:-10,showUndoButton:!1,data:[]};$.extend(d,q(g));j&&$.extend(d,j);this.settings=d;for(var f in a)a.hasOwnProperty(f)&&a[f].call(this,f);this.events.publish(e+".initializing");this.$controlbarUpper=$('<div style="padding:0 !important;margin:0 !important;width: 100% !important; height: 0 !important;margin-top:-1em !important;margin-bottom:1em !important;"></div>').appendTo(g);
this.isCanvasEmulator=!1;a=this.canvas=this.initializeCanvas(d);j=$(a);this.$controlbarLower=$('<div style="padding:0 !important;margin:0 !important;width: 100% !important; height: 0 !important;margin-top:-1.5em !important;margin-bottom:1.5em !important;"></div>').appendTo(g);this.canvasContext=a.getContext("2d");j.data(e+".this",this);g=(g=d.lineWidth)?g:Math.max(Math.round(a.width/400),2);d.lineWidth=g;this.lineCurveThreshold=3*d.lineWidth;d.cssclass&&""!=$.trim(d.cssclass)&&j.addClass(d.cssclass);
this.fatFingerCompensation=0;var g=function(b){var j,a,g=function(g){g=g.changedTouches&&0<g.changedTouches.length?g.changedTouches[0]:g;return new k(Math.round(g.pageX+j),Math.round(g.pageY+a)+b.fatFingerCompensation)},d=new y(750,function(){b.dataEngine.endStroke()});this.drawEndHandler=function(j){try{j.preventDefault()}catch(a){}d.clear();b.dataEngine.endStroke()};this.drawStartHandler=function(c){c.preventDefault();var s=$(b.canvas).offset();j=-1*s.left;a=-1*s.top;b.dataEngine.startStroke(g(c));
d.kick()};this.drawMoveHandler=function(j){j.preventDefault();b.dataEngine.inStroke&&(b.dataEngine.addToStroke(g(j)),d.kick())};return this}.call({},this),l=g.drawEndHandler,i=g.drawStartHandler,p=g.drawMoveHandler,h=this.canvas,j=$(h);this.isCanvasEmulator?(j.bind("mousemove."+e,p),j.bind("mouseup."+e,l),j.bind("mousedown."+e,i)):(h.ontouchstart=function(b){h.onmousedown=void 0;h.onmouseup=void 0;h.onmousemove=void 0;this.fatFingerCompensation=d.minFatFingerCompensation&&-3*d.lineWidth>d.minFatFingerCompensation?
-3*d.lineWidth:d.minFatFingerCompensation;i(b);h.ontouchend=l;h.ontouchstart=i;h.ontouchmove=p},h.onmousedown=function(b){h.ontouchstart=void 0;h.ontouchend=void 0;h.ontouchmove=void 0;i(b);h.onmousedown=i;h.onmouseup=l;h.onmousemove=p});b[e+".windowmouseup"]=c.subscribe(e+".windowmouseup",g.drawEndHandler);this.events.publish(e+".attachingEventHandlers");var n=this,b=d.width.toString(10),w=e;if("ratio"===b||"%"===b.split("")[b.length-1])this.eventTokens[w+".parentresized"]=c.subscribe(w+".parentresized",
function(b,j,a){return function(){var g=j.width();if(g!==a){for(var d in b)b.hasOwnProperty(d)&&(c.unsubscribe(b[d]),delete b[d]);var s=n.settings;n.$parent.children().remove();for(d in n)n.hasOwnProperty(d)&&delete n[d];d=s.data;var g=1*g/a,f=[],l,i,e,h,k,p;i=0;for(e=d.length;i<e;i++){p=d[i];l={x:[],y:[]};h=0;for(k=p.x.length;h<k;h++)l.x.push(p.x[h]*g),l.y.push(p.y[h]*g);f.push(l)}s.data=f;j[w](s)}}}(this.eventTokens,this.$parent,this.$parent.width(),1*this.canvas.width/this.canvas.height));this.resetCanvas(d.data);
this.events.publish(e+".initialized");return this}var e="jSignature",y=function(b,j){var a;this.kick=function(){clearTimeout(a);a=setTimeout(j,b)};this.clear=function(){clearTimeout(a)};return this},v=function(b){this.topics={};this.context=b?b:this;this.publish=function(b,a,g,d){if(this.topics[b]){var c=this.topics[b],f=Array.prototype.slice.call(arguments,1),l=[],i,e,h,p;e=0;for(h=c.length;e<h;e++)p=c[e],i=p[0],p[1]&&(p[0]=function(){},l.push(e)),i.apply(this.context,f);e=0;for(h=l.length;e<h;e++)c.splice(l[e],
1)}};this.subscribe=function(b,a,g){this.topics[b]?this.topics[b].push([a,g]):this.topics[b]=[[a,g]];return{topic:b,callback:a}};this.unsubscribe=function(b){if(this.topics[b.topic])for(var a=this.topics[b.topic],g=0,d=a.length;g<d;g++)a[g][0]===b.callback&&a.splice(g,1)}},z=function(b){var a=this.canvasContext,d=b.x[0],b=b.y[0],g=this.settings.lineWidth,c=a.fillStyle;a.fillStyle=a.strokeStyle;a.fillRect(d+g/-2,b+g/-2,g,g);a.fillStyle=c},u=function(b,a){var d=new k(b.x[a-1],b.y[a-1]),g=new k(b.x[a],
b.y[a]),c=d.getVectorToPoint(g);if(1<a){var f=new k(b.x[a-2],b.y[a-2]),l=f.getVectorToPoint(d),i;if(l.getLength()>this.lineCurveThreshold){i=2<a?(new k(b.x[a-3],b.y[a-3])).getVectorToPoint(f):new m(0,0);var e=0.35*l.getLength(),h=l.angleTo(i.reverse()),p=c.angleTo(l.reverse());i=(new m(i.x+l.x,i.y+l.y)).resizeTo(Math.max(0.05,h)*e);var n=(new m(l.x+c.x,l.y+c.y)).reverse().resizeTo(Math.max(0.05,p)*e),l=this.canvasContext,e=f.x,p=f.y,h=d.x,w=d.y,A=f.x+i.x,f=f.y+i.y;i=d.x+n.x;n=d.y+n.y;l.beginPath();
l.moveTo(e,p);l.bezierCurveTo(A,f,i,n,h,w);l.stroke()}}c.getLength()<=this.lineCurveThreshold&&(c=this.canvasContext,f=d.x,d=d.y,i=g.x,g=g.y,c.beginPath(),c.moveTo(f,d),c.lineTo(i,g),c.stroke())},x=function(b){var a=b.x.length-1;if(0<a){var d=new k(b.x[a],b.y[a]),g=new k(b.x[a-1],b.y[a-1]),c=g.getVectorToPoint(d);if(c.getLength()>this.lineCurveThreshold){if(1<a){var b=(new k(b.x[a-2],b.y[a-2])).getVectorToPoint(g),f=(new m(b.x+c.x,b.y+c.y)).resizeTo(c.getLength()/2),c=this.canvasContext,b=g.x,a=g.y,
l=d.x,i=d.y,e=g.x+f.x,g=g.y+f.y,f=d.x,d=d.y;c.beginPath();c.moveTo(b,a);c.bezierCurveTo(e,g,f,d,l,i)}else c=this.canvasContext,b=g.x,g=g.y,a=d.x,d=d.y,c.beginPath(),c.moveTo(b,g),c.lineTo(a,d);c.stroke()}}};r.prototype.resetCanvas=function(b){var a=this.canvas,d=this.settings,c=this.canvasContext,f=this.isCanvasEmulator,l=a.width,i=a.height;c.clearRect(0,0,l+30,i+30);c.shadowColor=c.fillStyle=d["background-color"];f&&c.fillRect(0,0,l+30,i+30);c.lineWidth=Math.ceil(parseInt(d.lineWidth,10));c.lineCap=
c.lineJoin="round";c.strokeStyle=d["decor-color"];c.shadowOffsetX=0;c.shadowOffsetY=0;var h=Math.round(i/5),p=1.5*h,k=i-h,l=l-1.5*h,i=i-h;c.beginPath();c.moveTo(p,k);c.lineTo(l,i);c.stroke();c.strokeStyle=d.color;f||(c.shadowColor=c.strokeStyle,c.shadowOffsetX=0.5*c.lineWidth,c.shadowOffsetY=-0.6*c.lineWidth,c.shadowBlur=0);b||(b=[]);c=this.dataEngine=new t(b,this,z,u,x);d.data=b;$(a).data(e+".data",b).data(e+".settings",d);var n=this.$parent,w=this.events,A=e;c.changed=function(){w.publish(A+".change");
n.trigger("change")};c.changed();return!0};r.prototype.initializeCanvas=function(b){var a=document.createElement("canvas"),c=$(a);b.width===b.height&&"ratio"===b.height&&(b.width="100%");c.css("margin",0).css("padding",0).css("border","none").css("height","ratio"===b.height||!b.height?1:b.height.toString(10)).css("width","ratio"===b.width||!b.width?1:b.width.toString(10));c.appendTo(this.$parent);"ratio"===b.height?c.css("height",Math.round(c.width()/b.sizeRatio)):"ratio"===b.width&&c.css("width",
Math.round(c.height()*b.sizeRatio));c.addClass(e);a.width=c.width();a.height=c.height();b=a;if(b.getContext)b=!1;else{var c=b.ownerDocument.parentWindow,d=c.FlashCanvas?b.ownerDocument.parentWindow.FlashCanvas:"undefined"===typeof FlashCanvas?void 0:FlashCanvas;if(d){b=d.initElement(b);d=1;c&&(c.screen&&c.screen.deviceXDPI&&c.screen.logicalXDPI)&&(d=1*c.screen.deviceXDPI/c.screen.logicalXDPI);if(1!==d)try{$(b).children("object").get(0).resize(Math.ceil(b.width*d),Math.ceil(b.height*d)),b.getContext("2d").scale(d,
d)}catch(f){}b=!0}else throw Error("Canvas element does not support 2d context. jSignature cannot proceed.");}this.isCanvasEmulator=b;a.onselectstart=function(b){b&&b.preventDefault&&b.preventDefault();b&&b.stopPropagation&&b.stopPropagation();return!1};return a};var p=window,h=function(b,a){var c=new Image,d=this;c.onload=function(){d.getContext("2d").drawImage(c,0,0,c.width<d.width?c.width:d.width,c.height<d.height?c.height:d.height)};c.src="data:"+a+","+b},a=function(b){this.find("canvas."+e).add(this.filter("canvas."+
e)).data(e+".this").resetCanvas(b);return this},f=function(b,a){if(void 0===a&&("string"===typeof b&&"data:"===b.substr(0,5))&&(a=b.slice(5).split(",")[0],b=b.slice(6+a.length),a===b))return;var c=this.find("canvas."+e).add(this.filter("canvas."+e));if(n.hasOwnProperty(a))0!==c.length&&n[a].call(c[0],b,a,function(b){return function(){return b.resetCanvas.apply(b,arguments)}}(c.data(e+".this")));else throw Error(e+" is unable to find import plugin with for format '"+String(a)+"'");return this},d=new v,
c=e,l,i=function(){d.publish(c+".parentresized")};$(p).bind("resize."+c,function(){l&&clearTimeout(l);l=setTimeout(i,500)}).bind("mouseup."+c,function(){d.publish(c+".windowmouseup")});var w={},A={"default":function(){return this.toDataURL()},"native":function(b){return b},image:function(){var b=this.toDataURL();if("string"===typeof b&&4<b.length&&"data:"===b.slice(0,5)&&-1!==b.indexOf(",")){var a=b.indexOf(",");return[b.slice(5,a),b.substr(a+1)]}return[]}},n={"native":function(b,a,c){c(b)},image:h,
"image/png;base64":h,"image/jpeg;base64":h,"image/jpg;base64":h},B={"export":A,"import":n,instance:w},C={init:function(b){return this.each(function(){var a,c=!1;for(a=this.parentNode;a&&!c;)c=a.body,a=a.parentNode;!c||new r(this,b,w)})},getSettings:function(){return this.find("canvas."+e).add(this.filter("canvas."+e)).data(e+".this").settings},clear:a,reset:a,addPlugin:function(b,a,c){B.hasOwnProperty(b)&&(B[b][a]=c);return this},listPlugins:function(b){var a=[];if(B.hasOwnProperty(b)){var b=B[b],
c;for(c in b)b.hasOwnProperty(c)&&a.push(c)}return a},getData:function(b){var a=this.find("canvas."+e).add(this.filter("canvas."+e));void 0===b&&(b="default");if(0!==a.length&&A.hasOwnProperty(b))return A[b].call(a.get(0),a.data(e+".data"))},importData:f,setData:f,globalEvents:function(){return d},events:function(){return this.find("canvas."+e).add(this.filter("canvas."+e)).data(e+".this").events}};$.fn[e]=function(b){if(!b||"object"===typeof b)return C.init.apply(this,arguments);if("string"===typeof b&&
C[b])return C[b].apply(this,Array.prototype.slice.call(arguments,1));$.error("Method "+String(b)+" does not exist on jQuery."+e)}})();
(function(){$.fn.jSignature("addPlugin","instance","UndoButton",function(q){this.events.subscribe("jSignature.attachingEventHandlers",function(){if(this.settings[q]){var m=this.settings[q];"function"!==typeof m&&(m=function(){var e=$('<input type="button" value="Undo last stroke" style="position:absolute;display:none;margin:0 !important;top:auto" />').appendTo(this.$controlbarLower),k=e.width();e.css("left",Math.round((this.canvas.width-k)/2));k!==e.width()&&e.width(k);return e});var k=m.call(this),
t=this;t.events.subscribe("jSignature.change",function(){t.dataEngine.data.length?k.show():k.hide()});var r=this,e=(this.events.topics.hasOwnProperty("jSignature.undo")?q:"jSignature")+".undo";k.bind("click",function(){r.events.publish(e)});r.events.subscribe(e,function(){var e=r.dataEngine.data;e.length&&(e.pop(),r.resetCanvas(e))})}})})})();
(function(){for(var q={},m={},k="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX".split(""),t=k.length/2,r=t-1;-1<r;r--)q[k[r]]=k[r+t],m[k[r+t]]=k[r];var e=function(e){for(var e=e.split(""),h=e.length,a=1;a<h;a++)e[a]=q[e[a]];return e.join("")},y=function(k){for(var h=[],a=0,f=1,d=k.length,c,l,i=0;i<d;i++)c=Math.round(k[i]),l=c-a,a=c,0>l&&0<f?(f=-1,h.push("Z")):0<l&&0>f&&(f=1,h.push("Y")),c=Math.abs(l),c>=t?h.push(e(c.toString(t))):h.push(c.toString(t));return h.join("")},v=function(e){for(var h=
[],e=e.split(""),a=e.length,f,d=1,c=[],l=0,i=0;i<a;i++)f=e[i],f in q||"Z"===f||"Y"===f?(0!==c.length&&(c=parseInt(c.join(""),t)*d+l,h.push(c),l=c),"Z"===f?(d=-1,c=[]):"Y"===f?(d=1,c=[]):c=[f]):c.push(m[f]);h.push(parseInt(c.join(""),t)*d+l);return h},z=function(e){for(var h=[],a=e.length,f,d=0;d<a;d++)f=e[d],h.push(y(f.x)),h.push(y(f.y));return h.join("_")},u=function(e){for(var h=[],e=e.split("_"),a=e.length/2,f=0;f<a;f++)h.push({x:v(e[2*f]),y:v(e[2*f+1])});return h},k=function(e){return["image/jsignature;base30",
z(e)]},r=function(e,h,a){"string"===typeof e&&("image/jsignature;base30"===e.substring(0,23).toLowerCase()&&(e=e.substring(24)),a(u(e)))};if(null==this.jQuery)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");var x=this.jQuery.fn.jSignature;x("addPlugin","export","base30",k);x("addPlugin","export","image/jsignature;base30",k);x("addPlugin","import","base30",r);x("addPlugin","import","image/jsignature;base30",r);this.jSignatureDebug&&(this.jSignatureDebug.base30=
{remapTailChars:e,compressstrokeleg:y,uncompressstrokeleg:v,compressstrokes:z,uncompressstrokes:u,charmap:q})}).call("undefined"!==typeof window?window:this);
(function(){function q(a,f){this.x=a;this.y=f;this.reverse=function(){return new this.constructor(-1*this.x,-1*this.y)};this._length=null;this.getLength=function(){this._length||(this._length=Math.sqrt(Math.pow(this.x,2)+Math.pow(this.y,2)));return this._length};var d=function(a){return Math.round(a/Math.abs(a))};this.resizeTo=function(a){if(0===this.x&&0===this.y)this._length=0;else if(0===this.x)this._length=a,this.y=a*d(this.y);else if(0===this.y)this._length=a,this.x=a*d(this.x);else{var f=Math.abs(this.y/
this.x),e=Math.sqrt(Math.pow(a,2)/(1+Math.pow(f,2))),f=f*e;this._length=a;this.x=e*d(this.x);this.y=f*d(this.y)}return this};this.angleTo=function(a){var d=this.getLength()*a.getLength();return 0===d?0:Math.acos(Math.min(Math.max((this.x*a.x+this.y*a.y)/d,-1),1))/Math.PI}}function m(a,f){this.x=a;this.y=f;this.getVectorToCoordinates=function(a,c){return new q(a-this.x,c-this.y)};this.getVectorFromCoordinates=function(a,c){return this.getVectorToCoordinates(a,c).reverse()};this.getVectorToPoint=function(a){return new q(a.x-
this.x,a.y-this.y)};this.getVectorFromPoint=function(a){return this.getVectorToPoint(a).reverse()}}function k(a,f){var d=Math.pow(10,f);return Math.round(a*d)/d}function t(a,f,d){var f=f+1,c=new m(a.x[f-1],a.y[f-1]),e=new m(a.x[f],a.y[f]),e=c.getVectorToPoint(e),i=new m(a.x[f-2],a.y[f-2]),c=i.getVectorToPoint(c);return c.getLength()>d?(d=2<f?(new m(a.x[f-3],a.y[f-3])).getVectorToPoint(i):new q(0,0),a=0.35*c.getLength(),i=c.angleTo(d.reverse()),f=e.angleTo(c.reverse()),d=(new q(d.x+c.x,d.y+c.y)).resizeTo(Math.max(0.05,
i)*a),e=(new q(c.x+e.x,c.y+e.y)).reverse().resizeTo(Math.max(0.05,f)*a),e=new q(c.x+e.x,c.y+e.y),["c",k(d.x,2),k(d.y,2),k(e.x,2),k(e.y,2),k(c.x,2),k(c.y,2)]):["l",k(c.x,2),k(c.y,2)]}function r(a,f){var d=a.x.length-1,c=new m(a.x[d],a.y[d]),e=new m(a.x[d-1],a.y[d-1]),c=e.getVectorToPoint(c);if(1<d&&c.getLength()>f){var d=(new m(a.x[d-2],a.y[d-2])).getVectorToPoint(e),e=c.angleTo(d.reverse()),i=0.35*c.getLength(),d=(new q(d.x+c.x,d.y+c.y)).resizeTo(Math.max(0.05,e)*i);return["c",k(d.x,2),k(d.y,2),k(c.x,
2),k(c.y,2),k(c.x,2),k(c.y,2)]}return["l",k(c.x,2),k(c.y,2)]}function e(a,e,d){for(var e=["M",k(a.x[0]-e,2),k(a.y[0]-d,2)],d=1,c=a.x.length-1;d<c;d++)e.push.apply(e,t(a,d,1));0<c?e.push.apply(e,r(a,d,1)):0===c&&e.push.apply(e,["l",1,1]);return e.join(" ")}function y(a){var f=['<?xml version="1.0" encoding="UTF-8" standalone="no"?>','<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'],d,c=a.length,l,i=[],h=[],k=l=d=0,n=0,p=[];if(0!==c){for(d=0;d<c;d++){k=
a[d];n=[];l={x:[],y:[]};for(var m=void 0,b=void 0,m=0,b=k.x.length;m<b;m++)n.push({x:k.x[m],y:k.y[m]});n=simplify(n,0.7,!0);m=0;for(b=n.length;m<b;m++)l.x.push(n[m].x),l.y.push(n[m].y);p.push(l);i=i.concat(l.x);h=h.concat(l.y)}a=Math.min.apply(null,i)-1;c=Math.max.apply(null,i)+1;i=Math.min.apply(null,h)-1;h=Math.max.apply(null,h)+1;k=0>a?0:a;n=0>i?0:i;d=c-a;l=h-i}f.push('<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'+d.toString()+'" height="'+l.toString()+'">');d=0;for(c=p.length;d<
c;d++)l=p[d],f.push('<path fill="none" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="'+e(l,k,n)+'"/>');f.push("</svg>");return f.join("")}function v(a){return[p,y(a)]}function z(a){return[h,x(y(a))]}var u=window;"use strict";("undefined"!=typeof exports?exports:u).simplify=function(a,e,d){e=void 0!==e?e*e:1;if(!d){for(var c=a.length,l,i=a[0],h=[i],d=1;d<c;d++){l=a[d];var k=l.x-i.x,n=l.y-i.y;k*k+n*n>e&&(h.push(l),i=l)}a=(i!==l&&h.push(l),h)}l=a;var d=l.length,
c=new ("undefined"!=typeof Uint8Array?Uint8Array:Array)(d),i=0,h=d-1,m,p,b=[],j=[],s=[];for(c[i]=c[h]=1;h;){n=0;for(k=i+1;k<h;k++){m=l[k];var g=l[i],r=l[h],q=g.x,t=g.y,g=r.x-q,u=r.y-t,v=void 0;if(0!==g||0!==u)v=((m.x-q)*g+(m.y-t)*u)/(g*g+u*u),1<v?(q=r.x,t=r.y):0<v&&(q+=g*v,t+=u*v);m=(g=m.x-q,u=m.y-t,g*g+u*u);m>n&&(p=k,n=m)}n>e&&(c[p]=1,b.push(i),j.push(p),b.push(p),j.push(h));i=b.pop();h=j.pop()}for(k=0;k<d;k++)c[k]&&s.push(l[k]);return a=s,a};if("function"!==typeof x)var x=function(a){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split(""),
d,c,h,i,k=0,m=0,n="",n=[];do d=a.charCodeAt(k++),c=a.charCodeAt(k++),h=a.charCodeAt(k++),i=d<<16|c<<8|h,d=i>>18&63,c=i>>12&63,h=i>>6&63,i&=63,n[m++]=e[d]+e[c]+e[h]+e[i];while(k<a.length);n=n.join("");a=a.length%3;return(a?n.slice(0,a-3):n)+"===".slice(a||3)};var p="image/svg+xml",h="image/svg+xml;base64";if("undefined"===typeof $)throw Error("We need jQuery for some of the functionality. jQuery is not detected. Failing to initialize...");u=$.fn.jSignature;u("addPlugin","export","svg",v);u("addPlugin",
"export",p,v);u("addPlugin","export","svgbase64",z);u("addPlugin","export",h,z)})();

View File

@@ -0,0 +1,4 @@
if (django && django.jQuery) {
var jQuery = django.jQuery;
var $ = django.jQuery;
}

View File

@@ -1,5 +0,0 @@
from .widgets import JSignatureWidgetTest
from .forms import JSignatureFormFieldTest
from .fields import JSignatureFieldTest
from .mixins import JSignatureFieldsMixinTest
from .utils import UtilsTest

View File

@@ -3,10 +3,10 @@
with jSignature jQuery plugin
"""
import json
import six
from django.conf import settings
from django.template.loader import render_to_string
from django.forms.widgets import HiddenInput
from django import forms
from django.core import validators
from django.core.exceptions import ValidationError
@@ -17,7 +17,14 @@ from jsignature.settings import JSIGNATURE_DEFAULT_CONFIG
JSIGNATURE_EMPTY_VALUES = validators.EMPTY_VALUES + ('[]', )
class JSignatureWidget(HiddenInput):
try:
from django.utils import six
string_types = six.string_types
except ImportError:
string_types = str
class JSignatureWidget(forms.HiddenInput):
"""
A widget handling a signature capture field with with jSignature
"""
@@ -26,9 +33,22 @@ class JSignatureWidget(HiddenInput):
# normal field, not a hidden one
is_hidden = False
class Media:
js = ('js/jSignature.min.js',
'js/django_jsignature.js')
@property
def media(self):
JSIGNATURE_JQUERY = getattr(settings, 'JSIGNATURE_JQUERY', 'custom')
files = ()
if JSIGNATURE_JQUERY == 'admin':
files = (
'admin/js/jquery.init.js',
'js/jsignature_admin_init.js',
)
elif JSIGNATURE_JQUERY != 'custom':
files = (JSIGNATURE_JQUERY,)
files += (
'js/jSignature.min.js',
'js/django_jsignature.js',
)
return forms.Media(js=files)
def __init__(self, attrs=None, jsignature_attrs=None):
super(JSignatureWidget, self).__init__(attrs)
@@ -53,13 +73,13 @@ class JSignatureWidget(HiddenInput):
""" Prepare value before effectively render widget """
if value in JSIGNATURE_EMPTY_VALUES:
return "[]"
elif isinstance(value, six.string_types):
elif isinstance(value, string_types):
return value
elif isinstance(value, list):
return json.dumps(value)
raise ValidationError('Invalid format.')
def render(self, name, value, attrs=None):
def render(self, name, value, attrs=None, renderer=None):
""" Render widget """
# Build config
jsign_id = self.build_jsignature_id(name)

View File

@@ -1,82 +0,0 @@
import os
import sys
import argparse
from django.conf import settings
class QuickDjangoTest(object):
"""
A quick way to run the Django test suite without a fully-configured project.
Example usage:
>>> QuickDjangoTest('app1', 'app2')
Based on a script published by Lukasz Dziedzia at:
http://stackoverflow.com/questions/3841725/how-to-launch-tests-for-django-reusable-app
"""
DIRNAME = os.path.dirname(__file__)
INSTALLED_APPS = (
)
def __init__(self, *args, **kwargs):
self.apps = args
self.run_tests()
def run_tests(self):
"""
Fire up the Django test suite developed for version 1.2
"""
settings.configure(
TEMPLATE_DIRS = ('jsignature/templates/',),
ROOT_URLCONF = 'jsignature.tests',
DEBUG = True,
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(self.DIRNAME, 'database.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
},
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
),
INSTALLED_APPS = self.INSTALLED_APPS + self.apps
)
# Setup is needed for Django >= 1.7
import django
if hasattr(django, 'setup'):
django.setup()
try:
from django.test.runner import DiscoverRunner
failures = DiscoverRunner().run_tests(self.apps, verbosity=1)
except ImportError:
# DjangoTestSuiteRunner has been deprecated in Django 1.7
from django.test.simple import DjangoTestSuiteRunner
failures = DjangoTestSuiteRunner().run_tests(self.apps, verbosity=1)
if failures: # pragma: no cover
sys.exit(failures)
if __name__ == '__main__':
"""
What do when the user hits this file from the shell.
Example usage:
$ python quicktest.py app1 app2
"""
parser = argparse.ArgumentParser(
usage="[args]",
description="Run Django tests on the provided applications."
)
parser.add_argument('apps', nargs='+', type=str)
args = parser.parse_args()
QuickDjangoTest(*args.apps)

View File

@@ -1,3 +1,2 @@
pillow
pyquery
six

15
runtests.py Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys
import django
from django.conf import settings
from django.test.utils import get_runner
if __name__ == "__main__":
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings'
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(["tests"])
sys.exit(bool(failures))

View File

@@ -5,16 +5,16 @@ here = os.path.abspath(os.path.dirname(__file__))
setup(
name='django-jsignature',
version='0.8',
version='0.9',
author='Florent Lebreton',
author_email='florent.lebreton@makina-corpus.com',
url='https://github.com/fle/django-jsignature',
download_url='https://github.com/fle/django-jsignature/tarball/0.8',
download_url='http://pypi.python.org/pypi/django-jsignature',
description='Use jSignature jQuery plugin in your django projects',
long_description=open(os.path.join(here, 'README.rst')).read() + '\n\n' +
open(os.path.join(here, 'CHANGES')).read(),
license='LPGL, see LICENSE file.',
install_requires=['Django'],
install_requires=['Django', 'pillow'],
packages=find_packages(),
include_package_data=True,
zip_safe=False,
@@ -25,14 +25,17 @@ setup(
'Intended Audience :: Developers',
'Environment :: Web Environment',
'Framework :: Django',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.0',
'Development Status :: 5 - Production/Stable',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4'
'Programming Language :: Python :: 3.5'
'Programming Language :: Python :: 3.6'
'Programming Language :: Python :: 3.7'
'Programming Language :: Python :: 3.8'
],
)

0
tests/__init__.py Normal file
View File

View File

@@ -1,5 +1,5 @@
""" Provides a dummy model implementing JSignatureFieldsMixin """
from ..mixins import JSignatureFieldsMixin
from jsignature.mixins import JSignatureFieldsMixin
class JSignatureTestModel(JSignatureFieldsMixin):

34
tests/settings.py Normal file
View File

@@ -0,0 +1,34 @@
import os
DEBUG = True
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SECRET_KEY = 'thisisntactuallysecretatall'
ALLOWED_HOSTS = ['*']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
# ROOT_URLCONF = 'tests.urls'
INSTALLED_APPS = [
'jsignature',
'tests',
]
PASSWORD_HASHERS = {
'django.contrib.auth.hashers.MD5PasswordHasher',
}
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
},
]

View File

@@ -1,11 +1,17 @@
import json
import six
from django.test import SimpleTestCase
from django.core.exceptions import ValidationError
from ..fields import JSignatureField
from ..forms import JSignatureField as JSignatureFormField
from jsignature.fields import JSignatureField
from jsignature.forms import JSignatureField as JSignatureFormField
try:
from django.utils import six
string_types = six.string_types
except ImportError:
string_types = str
class JSignatureFieldTest(SimpleTestCase):
@@ -31,21 +37,6 @@ class JSignatureFieldTest(SimpleTestCase):
val = 'foo'
self.assertRaises(ValidationError, f.to_python, val)
def test_from_db_value_empty(self):
f = JSignatureField()
self.assertIsNone(f.from_db_value(''))
def test_from_db_value_correct_value_json(self):
f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}]
val_str = '[{"x":[1,2], "y":[3,4]}]'
self.assertEquals(val, f.from_db_value(val_str))
def test_from_db_value_incorrect_value(self):
f = JSignatureField()
val = 'foo'
self.assertRaises(ValidationError, f.to_python, val)
def test_get_prep_value_empty(self):
f = JSignatureField()
for val in ['', [], '[]']:
@@ -55,7 +46,7 @@ class JSignatureFieldTest(SimpleTestCase):
f = JSignatureField()
val = [{"x": [1, 2], "y": [3, 4]}]
val_prep = f.get_prep_value(val)
self.assertIsInstance(val_prep, six.string_types)
self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep))
def test_get_prep_value_correct_values_json(self):
@@ -63,7 +54,7 @@ class JSignatureFieldTest(SimpleTestCase):
val = [{"x": [1, 2], "y": [3, 4]}]
val_str = '[{"x":[1,2], "y":[3,4]}]'
val_prep = f.get_prep_value(val_str)
self.assertIsInstance(val_prep, six.string_types)
self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep))
def test_get_prep_value_incorrect_values(self):

View File

@@ -1,8 +1,8 @@
from django.test import SimpleTestCase
from django.core.exceptions import ValidationError
from ..widgets import JSignatureWidget
from ..forms import JSignatureField
from jsignature.widgets import JSignatureWidget
from jsignature.forms import JSignatureField
class JSignatureFormFieldTest(SimpleTestCase):

View File

@@ -1,24 +1,10 @@
from datetime import date
from django.conf import settings
from django.db.models import loading
from django.test import SimpleTestCase
from django.core.management import call_command
from django.test import TestCase
from .models import JSignatureTestModel
class JSignatureFieldsMixinTest(SimpleTestCase):
def setUp(self):
self.old_installed_apps = settings.INSTALLED_APPS
settings.INSTALLED_APPS = list(settings.INSTALLED_APPS)
settings.INSTALLED_APPS.append('jsignature.tests')
loading.cache.loaded = False
call_command('syncdb', verbosity=0)
def tearDown(self):
settings.INSTALLED_APPS = self.old_installed_apps
class JSignatureFieldsMixinTest(TestCase):
def test_save_create(self):
# If an object is created signed, signature date must be set
signature_value = [{"x": [1, 2], "y": [3, 4]}]

View File

@@ -4,7 +4,7 @@ import imghdr
from PIL import Image
from django.test import SimpleTestCase
from ..utils import draw_signature
from jsignature.utils import draw_signature
DUMMY_VALUE = [{"x": [205, 210], "y": [59, 63]},
{"x": [205, 207], "y": [67, 64]}]

View File

@@ -1,16 +1,21 @@
import json
from pyquery import PyQuery as pq
import six
from django.test import SimpleTestCase
from django.test import SimpleTestCase, override_settings
from django.core.exceptions import ValidationError
from ..widgets import JSignatureWidget
from ..settings import JSIGNATURE_HEIGHT
from jsignature.widgets import JSignatureWidget
from jsignature.settings import JSIGNATURE_HEIGHT
try:
from django.utils import six
string_types = six.string_types
except ImportError:
string_types = str
class JSignatureWidgetTest(SimpleTestCase):
def test_default_media(self):
widget = JSignatureWidget()
media = widget.media
@@ -22,6 +27,13 @@ class JSignatureWidgetTest(SimpleTestCase):
media_css = list(media.render_css())
self.assertEquals([], media_css)
@override_settings(JSIGNATURE_JQUERY='admin')
def test_media_in_admin(self):
widget = JSignatureWidget()
media = widget.media
media_js = list(media.render_js())
self.assertEqual(4, len(media_js))
def test_init(self):
w = JSignatureWidget()
self.assertEquals({}, w.jsignature_attrs)
@@ -49,7 +61,7 @@ class JSignatureWidgetTest(SimpleTestCase):
w = JSignatureWidget()
val = [{"x": [1, 2], "y": [3, 4]}]
val_prep = w.prep_value(val)
self.assertIsInstance(val_prep, six.string_types)
self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep))
def test_prep_value_correct_values_json(self):
@@ -57,7 +69,7 @@ class JSignatureWidgetTest(SimpleTestCase):
val = [{"x": [1, 2], "y": [3, 4]}]
val_str = '[{"x":[1,2], "y":[3,4]}]'
val_prep = w.prep_value(val_str)
self.assertIsInstance(val_prep, six.string_types)
self.assertIsInstance(val_prep, string_types)
self.assertEquals(val, json.loads(val_prep))
def test_prep_value_incorrect_values(self):

23
tox.ini Normal file
View File

@@ -0,0 +1,23 @@
[tox]
envlist =
{py27,py35,py36}-django111,
{py35,py36,py37,py38}-django22,
{py36,py37,py38}-django{30,master}
[testenv]
deps=
django111: Django>=1.11,<2.0
django22: Django>=2.2,<3.0
django30: Django>=3.0,<4.0
djangomaster: https://github.com/django/django/archive/master.tar.gz
-r requirements.txt
coverage
commands= coverage run ./runtests.py
[travis:env]
DJANGO =
1.11: django111
2.2: django22
3.0: django30
master: djangomaster