Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae58c7f725 | ||
|
|
cf9a37a009 | ||
|
|
382c824cca | ||
|
|
63c3d7d2dc | ||
|
|
ea3800375c | ||
|
|
a42a9ca543 | ||
|
|
9e81795f99 | ||
|
|
7fe2560910 | ||
|
|
e92132349c | ||
|
|
a740767a06 | ||
|
|
ea249473f1 | ||
|
|
bea517d746 | ||
|
|
4dd9e5f25f | ||
|
|
72e9f3d6da | ||
|
|
7601258ea8 | ||
|
|
3f5d877b6a | ||
|
|
cf2ad535e1 | ||
|
|
2be2c4dcc6 | ||
|
|
d08e0ba276 | ||
|
|
a6946d160d | ||
|
|
ac8c20abb3 | ||
|
|
622f3ea10b | ||
|
|
c5f7c10d23 | ||
|
|
d0ae3a9c43 | ||
|
|
39f2ba9533 | ||
|
|
33b43b98b0 | ||
|
|
9923614d6a | ||
|
|
6da81f2115 | ||
|
|
d12b1e80ac | ||
|
|
880033d3a5 | ||
|
|
c15074873b | ||
|
|
8e912f9738 | ||
|
|
70b4f2e9b6 | ||
|
|
124fe21d06 | ||
|
|
738eb869fc | ||
|
|
32566873a4 | ||
|
|
bbde0d7005 | ||
|
|
11ae827643 | ||
|
|
337a8f9a08 | ||
|
|
51005db0ca | ||
|
|
02e78b1a6c | ||
|
|
b29c5964af | ||
|
|
5c7f1d7037 | ||
|
|
7666e781e5 | ||
|
|
27eeca4f8f | ||
|
|
a8bf966933 | ||
|
|
202df097b0 | ||
|
|
0de152f292 | ||
|
|
1fd41a622a | ||
|
|
b87ba745e7 | ||
|
|
69e81bd808 | ||
|
|
91b0e4d400 | ||
|
|
a86cc67520 | ||
|
|
271b4239a2 | ||
|
|
ca5ba2d0f8 | ||
|
|
95860405f3 | ||
|
|
ff2494a33e | ||
|
|
9c8ad2087e | ||
|
|
a8ef50d8d0 | ||
|
|
5c17f753e7 | ||
|
|
ed42468767 | ||
|
|
a19d0f9545 | ||
|
|
c4bb0b8fd1 | ||
|
|
86791e12f3 |
2
.coveragerc
Normal file
2
.coveragerc
Normal file
@@ -0,0 +1,2 @@
|
||||
[run]
|
||||
source = jsignature
|
||||
66
.github/workflows/actions.yml
vendored
Normal file
66
.github/workflows/actions.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Run Tests
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
django_version:
|
||||
- '2.2'
|
||||
- '3.2'
|
||||
- '4.0'
|
||||
python-version:
|
||||
- 3.6
|
||||
- 3.7
|
||||
- 3.8
|
||||
- 3.9
|
||||
- "3.10"
|
||||
exclude:
|
||||
- django_version: '2.2'
|
||||
python-version: '3.10'
|
||||
|
||||
- django_version: '4.0'
|
||||
python-version: '3.6'
|
||||
|
||||
- django_version: '4.0'
|
||||
python-version: '3.7'
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
# This path is specific to Ubuntu
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ matrix.django_version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e .
|
||||
pip install -U flake8 coveralls argparse
|
||||
pip install -U Django~=${{ matrix.django_version }}
|
||||
|
||||
- name: Lint with flake8
|
||||
run: flake8 --ignore=E501,W504 jsignature
|
||||
|
||||
- name: Test Django
|
||||
run: |
|
||||
python -W error::DeprecationWarning -W error::PendingDeprecationWarning \
|
||||
-m coverage run ./runtests.py
|
||||
|
||||
- name: Coverage
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
coveralls --service=github
|
||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
*.pyc
|
||||
.coverage
|
||||
htmlcov
|
||||
*.egg-info
|
||||
.tox
|
||||
example.db
|
||||
/dist
|
||||
/build
|
||||
55
CHANGES
Normal file
55
CHANGES
Normal file
@@ -0,0 +1,55 @@
|
||||
=========
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.11 (2022-01-17)
|
||||
==================
|
||||
|
||||
** New **
|
||||
|
||||
- Django 4.0 compatibility.
|
||||
- Moved to Github Actions for testing.
|
||||
- Added flake8 in CI.
|
||||
- Dropped support for python < 3.6
|
||||
- Dropped support for Django 1.11, 2.0, 2.1, 3.0, 3.1.
|
||||
|
||||
|
||||
0.10 (2020-07-26)
|
||||
==================
|
||||
|
||||
** New **
|
||||
|
||||
- Add template filter to easily render a signature structure as base64 url.
|
||||
- Add a full example of front-office usage.
|
||||
|
||||
|
||||
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)
|
||||
==================
|
||||
|
||||
** New **
|
||||
|
||||
- Add support for Python 3 (@Gagaro)
|
||||
- Add support for Django 1.7 (@Gagaro)
|
||||
|
||||
|
||||
0.7.6 (2014-11-26)
|
||||
==================
|
||||
|
||||
** New features **
|
||||
|
||||
- A setting to display (or not) the reset button has been added (@jsayles)
|
||||
|
||||
** Internal changes **
|
||||
|
||||
- Rendering is now based on a template (@andybak)
|
||||
- Javascript is properly initialized (@andybak)
|
||||
@@ -1,4 +1,4 @@
|
||||
include README.rst LICENSE
|
||||
recursive-include jsignature/templates *.html
|
||||
include README.rst LICENSE CHANGES
|
||||
recursive-include jsignature/static *.js
|
||||
recursive-include jsignature/static *.css
|
||||
recursive-include jsignature/locale *.mo
|
||||
recursive-include jsignature/templates *.html
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
django-jsignature
|
||||
=================
|
||||
|
||||
Use jSignature jQuery plugin in your django projects
|
||||
206
README.rst
206
README.rst
@@ -1,12 +1,208 @@
|
||||
==================
|
||||
django-jsignature
|
||||
==================
|
||||
|
||||
A simple way to use `jSignature jQuery plugin <https://github.com/brinley/jSignature/blob/master/README.md>`_ in your `Django <https://www.djangoproject.com>`_ projects.
|
||||
|
||||
It provides:
|
||||
|
||||
* A form field and a form widget to handle jquery plugin through a Django form;
|
||||
* A model field to store a captured signature;
|
||||
* A mixin adding two fields (signature / signature_date) in any of your Django models;
|
||||
* A mixin adding two fields (signature / signature_date) in any of your Django models.
|
||||
* A template filter to render signatures as base64 image urls.
|
||||
|
||||
.. 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
|
||||
:alt: Build status
|
||||
|
||||
.. image:: https://coveralls.io/repos/fle/django-jsignature/badge.png
|
||||
:target: https://coveralls.io/r/fle/django-jsignature
|
||||
:alt: Coverage status
|
||||
|
||||
.. image:: https://github.com/fle/django-jsignature/blob/master/screen.png
|
||||
|
||||
==================
|
||||
Installation
|
||||
==================
|
||||
|
||||
::
|
||||
|
||||
pip install django-jsignature
|
||||
|
||||
==================
|
||||
Usage
|
||||
==================
|
||||
|
||||
* Add ``jsignature`` to your ``INSTALLED_APPS``:
|
||||
|
||||
::
|
||||
|
||||
# settings.py
|
||||
INSTALLED_APPS = (
|
||||
...
|
||||
'jsignature',
|
||||
)
|
||||
|
||||
* Use provided model field (for easy storage):
|
||||
|
||||
::
|
||||
|
||||
# models.py
|
||||
from django.db import models
|
||||
from jsignature.fields import JSignatureField
|
||||
|
||||
class SignatureModel(models.Model):
|
||||
signature = JSignatureField()
|
||||
|
||||
* In your form template
|
||||
|
||||
::
|
||||
|
||||
{{ form.media }}
|
||||
<form action="" method="post">
|
||||
{{ form }}
|
||||
<input type="submit" value="Save" />
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
|
||||
* Render image from db value in your display template:
|
||||
|
||||
::
|
||||
|
||||
{# yourtemplate.html #}
|
||||
{% load jsignature_filters %}
|
||||
|
||||
<img src="{{ obj.signature|signature_base64 }}" alt="{{ obj }}" />
|
||||
|
||||
|
||||
* 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.
|
||||
|
||||
It is strongly suggested to take example from ``example_project``, which is
|
||||
`located in this repo <https://github.com/fle/django-jsignature/tree/master/example_project>`_
|
||||
|
||||
==================
|
||||
Customization
|
||||
==================
|
||||
|
||||
JSignature plugin options are available in python:
|
||||
|
||||
* Globally, in your settings:
|
||||
|
||||
::
|
||||
|
||||
# settings.py
|
||||
JSIGNATURE_WIDTH = 500
|
||||
JSIGNATURE_HEIGHT = 200
|
||||
|
||||
* Specifically, in your form:
|
||||
|
||||
::
|
||||
|
||||
# forms.py
|
||||
from jsignature.forms import JSignatureField
|
||||
from jsignature.widgets import JSignatureWidget
|
||||
|
||||
JSignatureField(widget=JSignatureWidget(jsignature_attrs={'color': '#CCC'}))
|
||||
|
||||
Available settings are:
|
||||
|
||||
* ``JSIGNATURE_WIDTH`` (width)
|
||||
* ``JSIGNATURE_HEIGHT`` (height)
|
||||
* ``JSIGNATURE_COLOR`` (color)
|
||||
* ``JSIGNATURE_BACKGROUND_COLOR`` (background-color)
|
||||
* ``JSIGNATURE_DECOR_COLOR`` (decor-color)
|
||||
* ``JSIGNATURE_LINE_WIDTH`` (lineWidth)
|
||||
* ``JSIGNATURE_UNDO_BUTTON`` (UndoButton)
|
||||
* ``JSIGNATURE_RESET_BUTTON`` (ResetButton)
|
||||
|
||||
==================
|
||||
In your models
|
||||
==================
|
||||
|
||||
If you want to store signatures easily, a provided mixin gives a ``signature``
|
||||
and a ``signature_date`` that update themselves:
|
||||
|
||||
::
|
||||
|
||||
from django.db import models
|
||||
from jsignature.mixins import JSignatureFieldsMixin
|
||||
|
||||
class JSignatureModel(JSignatureFieldsMixin):
|
||||
name = models.CharField()
|
||||
|
||||
|
||||
==================
|
||||
In your forms
|
||||
==================
|
||||
|
||||
* If you need more precise handling of the form field, you can use it directly:
|
||||
|
||||
::
|
||||
|
||||
# forms.py
|
||||
from django import forms
|
||||
from jsignature.forms import JSignatureField
|
||||
|
||||
class SignatureForm(forms.Form):
|
||||
signature = JSignatureField()
|
||||
|
||||
|
||||
* And upon saving, have direct access to the image with ``draw_signature()``
|
||||
|
||||
::
|
||||
|
||||
# views.py
|
||||
from jsignature.utils import draw_signature
|
||||
from myapp.forms import SignatureForm
|
||||
|
||||
def my_view(request):
|
||||
form = SignatureForm(request.POST or None)
|
||||
if form.is_valid():
|
||||
signature = form.cleaned_data.get('signature')
|
||||
if signature:
|
||||
# as an image
|
||||
signature_picture = draw_signature(signature)
|
||||
# or as a file
|
||||
signature_file_path = draw_signature(signature, as_file=True)
|
||||
|
||||
|
||||
==================
|
||||
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/>`_, you can also
|
||||
`login to the admin <http://127.0.0.1:8000/admin>`_ with the credentials your
|
||||
provided.
|
||||
|
||||
==================
|
||||
Authors
|
||||
==================
|
||||
|
||||
* Florent Lebreton <florent.lebreton@makina-corpus.com> (original author)
|
||||
* Sébastien Corbin <sebastien.corbin@makina-corpus.com> (maintainer)
|
||||
|
||||
|makinacom|_
|
||||
|
||||
.. |makinacom| image:: http://depot.makina-corpus.org/public/logo.gif
|
||||
.. _makinacom: http://www.makina-corpus.com
|
||||
|
||||
|
||||
0
example_project/__init__.py
Normal file
0
example_project/__init__.py
Normal file
5
example_project/admin.py
Normal file
5
example_project/admin.py
Normal 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
10
example_project/manage.py
Executable 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)
|
||||
26
example_project/migrations/0001_initial.py
Normal file
26
example_project/migrations/0001_initial.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
||||
0
example_project/migrations/__init__.py
Normal file
0
example_project/migrations/__init__.py
Normal file
5
example_project/models.py
Normal file
5
example_project/models.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from jsignature.mixins import JSignatureFieldsMixin
|
||||
|
||||
|
||||
class ExampleModel(JSignatureFieldsMixin):
|
||||
pass
|
||||
67
example_project/settings.py
Normal file
67
example_project/settings.py
Normal 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'
|
||||
39
example_project/templates/base.html
Normal file
39
example_project/templates/base.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
|
||||
<title>Example project</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="container">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'admin:index' %}">Admnistration</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'list' %}">List</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'create' %}">Create</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||
{% block extra_media %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block extra_media %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form action="" method="post">
|
||||
{{ form }}
|
||||
<input type="submit" value="Save" class="btn btn-success" />
|
||||
<a href="{% url 'list' %}" class="btn btn-danger">Cancel</a>
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}{% load jsignature_filters %}
|
||||
|
||||
{% block content %}
|
||||
<h1>List</h1>
|
||||
|
||||
<ul>
|
||||
{% for obj in object_list %}
|
||||
<li>
|
||||
<dl>
|
||||
<dt>Raw data (from db)</dt>
|
||||
<dd>{{ obj.signature }}</dd>
|
||||
<dt>As image</dt>
|
||||
<dd>
|
||||
<img src="{{ obj.signature|signature_base64 }}" alt="{{ obj }}" />
|
||||
<a href="{% url 'update' obj.pk %}" class="btn btn-primary">
|
||||
Update
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a href="{% url 'create' %}" class="btn btn-primary">Create</a>
|
||||
{% endblock %}
|
||||
13
example_project/urls.py
Normal file
13
example_project/urls.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
from example_project import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.ExampleListView.as_view(), name='list'),
|
||||
path('create', views.ExampleCreateView.as_view(), name='create'),
|
||||
path('update/<int:pk>', views.ExampleUpdateView.as_view(), name='update'),
|
||||
path('admin', admin.site.urls),
|
||||
]
|
||||
20
example_project/views.py
Normal file
20
example_project/views.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from django.urls import reverse_lazy
|
||||
from django.views import generic
|
||||
|
||||
from example_project.models import ExampleModel
|
||||
|
||||
|
||||
class ExampleCreateView(generic.CreateView):
|
||||
model = ExampleModel
|
||||
fields = '__all__'
|
||||
success_url = reverse_lazy('list')
|
||||
|
||||
|
||||
class ExampleUpdateView(generic.UpdateView):
|
||||
model = ExampleModel
|
||||
fields = '__all__'
|
||||
success_url = reverse_lazy('list')
|
||||
|
||||
|
||||
class ExampleListView(generic.ListView):
|
||||
model = ExampleModel
|
||||
@@ -3,17 +3,28 @@
|
||||
with jSignature jQuery plugin
|
||||
"""
|
||||
import json
|
||||
|
||||
from django.db import models
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
from .forms import JSignatureField as JSignatureFormField
|
||||
|
||||
from .forms import (
|
||||
JSignatureField as JSignatureFormField,
|
||||
JSIGNATURE_EMPTY_VALUES,
|
||||
)
|
||||
|
||||
try:
|
||||
from django.utils import six
|
||||
|
||||
string_types = six.string_types
|
||||
except ImportError:
|
||||
string_types = str
|
||||
|
||||
|
||||
class JSignatureField(models.Field):
|
||||
"""
|
||||
A model field handling a signature captured with jSignature
|
||||
"""
|
||||
description = "A signature captured with jSignature"
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'TextField'
|
||||
@@ -23,7 +34,7 @@ class JSignatureField(models.Field):
|
||||
Validates that the input can be red as a JSON object. Returns a Python
|
||||
datetime.date object.
|
||||
"""
|
||||
if value in validators.EMPTY_VALUES:
|
||||
if value in JSIGNATURE_EMPTY_VALUES:
|
||||
return None
|
||||
elif isinstance(value, list):
|
||||
return value
|
||||
@@ -33,12 +44,9 @@ class JSignatureField(models.Field):
|
||||
raise ValidationError('Invalid JSON format.')
|
||||
|
||||
def get_prep_value(self, value):
|
||||
return self.to_python(value)
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
if value in validators.EMPTY_VALUES:
|
||||
if value in JSIGNATURE_EMPTY_VALUES:
|
||||
return None
|
||||
elif isinstance(value, basestring):
|
||||
elif isinstance(value, string_types):
|
||||
return value
|
||||
elif isinstance(value, list):
|
||||
return json.dumps(value)
|
||||
@@ -48,7 +56,3 @@ class JSignatureField(models.Field):
|
||||
defaults = {'form_class': JSignatureFormField}
|
||||
defaults.update(kwargs)
|
||||
return super(JSignatureField, self).formfield(**defaults)
|
||||
|
||||
|
||||
from south.modelsinspector import add_introspection_rules
|
||||
add_introspection_rules([], ["jsignature.fields.JSignatureField"])
|
||||
|
||||
@@ -8,6 +8,9 @@ from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
from .widgets import JSignatureWidget
|
||||
|
||||
JSIGNATURE_EMPTY_VALUES = validators.EMPTY_VALUES + ('[]', )
|
||||
|
||||
|
||||
class JSignatureField(Field):
|
||||
"""
|
||||
A field handling a signature capture field with with jSignature
|
||||
@@ -16,10 +19,10 @@ class JSignatureField(Field):
|
||||
|
||||
def to_python(self, value):
|
||||
"""
|
||||
Validates that the input can be red as a JSON object. Returns a Python
|
||||
datetime.date object.
|
||||
Validates that the input can be red as a JSON object.
|
||||
Returns a Python list (JSON object unserialized).
|
||||
"""
|
||||
if value in validators.EMPTY_VALUES:
|
||||
if value in JSIGNATURE_EMPTY_VALUES:
|
||||
return None
|
||||
try:
|
||||
return json.loads(value)
|
||||
|
||||
BIN
jsignature/locale/fr/LC_MESSAGES/django.mo
Normal file
BIN
jsignature/locale/fr/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
30
jsignature/locale/fr/LC_MESSAGES/django.po
Normal file
30
jsignature/locale/fr/LC_MESSAGES/django.po
Normal file
@@ -0,0 +1,30 @@
|
||||
# French translation for django-jsignature package.
|
||||
# Copyright (C) 2013
|
||||
# This file is distributed under the same license as the django-jsignature package.
|
||||
# Florent Lebreton <florent.lebreton@makina-corpus.com>
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2013-09-18 11:10+0200\n"
|
||||
"PO-Revision-Date: 2013-09-18 11:13+0100\n"
|
||||
"Last-Translator: Florent Lebreton <lebreton.florent@wanadoo.fr>\n"
|
||||
"Language-Team: contact@makina-corpus.com\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Generator: Poedit 1.5.4\n"
|
||||
|
||||
#: mixins.py:13
|
||||
msgid "Signature"
|
||||
msgstr "Signature"
|
||||
|
||||
#: mixins.py:17
|
||||
msgid "Signature date"
|
||||
msgstr "Date de signature"
|
||||
|
||||
#: widgets.py:65
|
||||
msgid "Reset"
|
||||
msgstr "Réinitialiser"
|
||||
@@ -2,11 +2,13 @@
|
||||
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 _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from .fields import JSignatureField
|
||||
|
||||
|
||||
class JSignatureFieldsMixin(models.Model):
|
||||
""" Mixin class providing fields to store a signature with jSignature """
|
||||
signature = JSignatureField(
|
||||
@@ -24,11 +26,10 @@ class JSignatureFieldsMixin(models.Model):
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
is_new = self.pk is None
|
||||
original = not is_new and self.__class__.objects.get(pk=self.pk) or None
|
||||
original = not is_new and self.__class__.objects.get(pk=self.pk)
|
||||
|
||||
print self.signature
|
||||
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
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
from django.conf import settings
|
||||
|
||||
JSIGNATURE_WIDTH = getattr(settings, 'JSIGNATURE_WIDTH', 'ratio')
|
||||
JSIGNATURE_HEIGHT = getattr(settings, 'JSIGNATURE_HEIGHT', 'ratio')
|
||||
JSIGNATURE_COLOR = getattr(settings, 'JSIGNATURE_COLOR', '#000')
|
||||
JSIGNATURE_BACKGROUND_COLOR = getattr(settings, 'JSIGNATURE_BACKGROUND_COLOR', '#FFF')
|
||||
JSIGNATURE_DECOR_COLOR = getattr(settings, 'JSIGNATURE_DECOR_COLOR', '#DDD')
|
||||
JSIGNATURE_LINE_WIDTH = getattr(settings, 'JSIGNATURE_LINE_WIDTH', 0)
|
||||
JSIGNATURE_UNDO_BUTTON = getattr(settings, 'JSIGNATURE_UNDO_BUTTON', False)
|
||||
JSIGNATURE_WIDTH = getattr(
|
||||
settings, 'JSIGNATURE_WIDTH', 'ratio')
|
||||
JSIGNATURE_HEIGHT = getattr(
|
||||
settings, 'JSIGNATURE_HEIGHT', 'ratio')
|
||||
JSIGNATURE_COLOR = getattr(
|
||||
settings, 'JSIGNATURE_COLOR', '#000')
|
||||
JSIGNATURE_BACKGROUND_COLOR = getattr(
|
||||
settings, 'JSIGNATURE_BACKGROUND_COLOR', '#FFF')
|
||||
JSIGNATURE_DECOR_COLOR = getattr(
|
||||
settings, 'JSIGNATURE_DECOR_COLOR', '#DDD')
|
||||
JSIGNATURE_LINE_WIDTH = getattr(
|
||||
settings, 'JSIGNATURE_LINE_WIDTH', 0)
|
||||
JSIGNATURE_UNDO_BUTTON = getattr(
|
||||
settings, 'JSIGNATURE_UNDO_BUTTON', False)
|
||||
JSIGNATURE_RESET_BUTTON = getattr(
|
||||
settings, 'JSIGNATURE_RESET_BUTTON', True)
|
||||
|
||||
JSIGNATURE_JQUERY = getattr(
|
||||
settings, 'JSIGNATURE_JQUERY', 'custom')
|
||||
|
||||
JSIGNATURE_DEFAULT_CONFIG = {
|
||||
'width': JSIGNATURE_WIDTH,
|
||||
@@ -16,4 +28,5 @@ JSIGNATURE_DEFAULT_CONFIG = {
|
||||
'decor-color': JSIGNATURE_DECOR_COLOR,
|
||||
'lineWidth': JSIGNATURE_LINE_WIDTH,
|
||||
'UndoButton': JSIGNATURE_UNDO_BUTTON,
|
||||
'ResetButton': JSIGNATURE_RESET_BUTTON,
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
$(document).ready(function() {
|
||||
(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 */
|
||||
$(document).delegate(".jsign-container", "change", function(e) {
|
||||
$(".jsign-container").on("change", function(e) {
|
||||
var jSignature_data = $(this).jSignature('getData', 'native');
|
||||
var django_field_name = $(this).attr('id').split('_')[1];
|
||||
var django_field_name = $(this).attr('id').split(/_(.+)/)[1];
|
||||
$('#id_' + django_field_name).val(JSON.stringify(jSignature_data));
|
||||
});
|
||||
|
||||
/* Bind clear button */
|
||||
$(document).delegate(".jsign-wrapper input", "click", function(e) {
|
||||
$(".jsign-wrapper input").on("click", function(e) {
|
||||
$(this).siblings('.jsign-container').jSignature('reset');
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
})(jQuery || django.jQuery)
|
||||
|
||||
100
jsignature/static/js/jSignature.min.js
vendored
Normal file → Executable file
100
jsignature/static/js/jSignature.min.js
vendored
Normal file → Executable file
@@ -1,11 +1,16 @@
|
||||
/*
|
||||
|
||||
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>
|
||||
|
||||
|
||||
Simplify.js BSD
|
||||
(c) 2012, Vladimir Agafonkin
|
||||
mourner.github.com/simplify-js
|
||||
|
||||
|
||||
base64 encoder
|
||||
MIT, GPL
|
||||
http://phpjs.org/functions/base64_encode
|
||||
@@ -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)})();
|
||||
4
jsignature/static/js/jsignature_admin_init.js
Normal file
4
jsignature/static/js/jsignature_admin_init.js
Normal file
@@ -0,0 +1,4 @@
|
||||
if (django && django.jQuery) {
|
||||
var jQuery = django.jQuery;
|
||||
var $ = django.jQuery;
|
||||
}
|
||||
10
jsignature/templates/jsignature/widget.html
Normal file
10
jsignature/templates/jsignature/widget.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<div class='jsign-wrapper'>
|
||||
{{ hidden }}
|
||||
<div id='{{ jsign_id }}'
|
||||
data-config='{{ js_config }}'
|
||||
data-initial-value='{{ value }}'
|
||||
class='jsign-container'></div>
|
||||
{% if config.ResetButton %}
|
||||
<input type='button' value='{{ reset_btn_text }}' class="btn">
|
||||
{% endif %}
|
||||
</div>
|
||||
0
jsignature/templatetags/__init__.py
Normal file
0
jsignature/templatetags/__init__.py
Normal file
21
jsignature/templatetags/jsignature_filters.py
Normal file
21
jsignature/templatetags/jsignature_filters.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import base64
|
||||
import io
|
||||
|
||||
from django import template
|
||||
from django.utils.encoding import iri_to_uri
|
||||
|
||||
from jsignature.utils import draw_signature
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def signature_base64(value):
|
||||
if value is None or not isinstance(value, str):
|
||||
return ""
|
||||
in_mem_file = io.BytesIO()
|
||||
draw_signature(value).save(in_mem_file, format="PNG")
|
||||
in_mem_file.seek(0)
|
||||
return "data:image/png;base64,{}".format(
|
||||
iri_to_uri(base64.b64encode(in_mem_file.read()).decode('utf8'))
|
||||
)
|
||||
@@ -3,37 +3,43 @@
|
||||
https://github.com/zivezab/django-autograph/blob/master/autograph/utils.py
|
||||
"""
|
||||
import json
|
||||
import cStringIO
|
||||
from itertools import chain
|
||||
from tempfile import NamedTemporaryFile
|
||||
from PIL import Image, ImageDraw, ImageOps
|
||||
|
||||
AA = 5 # super sampling gor antialiasing
|
||||
|
||||
|
||||
def draw_signature(data, as_file=False):
|
||||
""" Draw signature based on lines stored in json_string.
|
||||
`data` can be a json object (list in fact) or a json string
|
||||
if `as_file` is True, a temp file is returned instead of Image instance
|
||||
"""
|
||||
|
||||
def _remove_empty_pts(pt):
|
||||
return {
|
||||
'x': list(filter(lambda n: n is not None, pt['x'])),
|
||||
'y': list(filter(lambda n: n is not None, pt['y']))
|
||||
}
|
||||
|
||||
if type(data) is str:
|
||||
drawing = json.loads(data)
|
||||
drawing = json.loads(data, object_hook=_remove_empty_pts)
|
||||
elif type(data) is list:
|
||||
drawing = data
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
# Compute box
|
||||
width = max(chain(*[d['x'] for d in data])) + 10
|
||||
height = max(chain(*[d['y'] for d in data])) + 10
|
||||
width = int(round(max(chain(*[d['x'] for d in drawing])))) + 10
|
||||
height = int(round(max(chain(*[d['y'] for d in drawing])))) + 10
|
||||
|
||||
# Draw image
|
||||
im = Image.new("RGBA", (width*AA, height*AA))
|
||||
im = Image.new("RGBA", (width * AA, height * AA))
|
||||
draw = ImageDraw.Draw(im)
|
||||
for line in drawing:
|
||||
len_line = len(line['x'])
|
||||
points = [(line['x'][i]*AA, line['y'][i]*AA) for i in range(0, len_line)]
|
||||
draw.line(points, fill="#000", width=2*AA)
|
||||
points = [(line['x'][i] * AA, line['y'][i] * AA)
|
||||
for i in range(0, len_line)]
|
||||
draw.line(points, fill="#000", width=2 * AA)
|
||||
im = ImageOps.expand(im)
|
||||
# Smart crop
|
||||
bbox = im.getbbox()
|
||||
@@ -45,7 +51,6 @@ def draw_signature(data, as_file=False):
|
||||
if as_file:
|
||||
ret = im._dump(format='PNG')
|
||||
else:
|
||||
ret = img
|
||||
ret = im
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@@ -3,39 +3,100 @@
|
||||
with jSignature jQuery plugin
|
||||
"""
|
||||
import json
|
||||
from django.forms.widgets import HiddenInput
|
||||
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django import forms
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from jsignature.settings import JSIGNATURE_DEFAULT_CONFIG
|
||||
|
||||
class JSignatureWidget(HiddenInput):
|
||||
JSIGNATURE_EMPTY_VALUES = validators.EMPTY_VALUES + ('[]', )
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
class Media:
|
||||
js = ('js/jSignature.min.js',
|
||||
'js/django_jsignature.js')
|
||||
# Actually, this widget has a display so we want it to behave like a
|
||||
# normal field, not a hidden one
|
||||
is_hidden = False
|
||||
|
||||
@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)
|
||||
# Store jSignature js config
|
||||
self.jsignature_attrs = jsignature_attrs or {}
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
|
||||
# Build config
|
||||
jsign_id = 'jsign_%s' % name
|
||||
def build_jsignature_config(self):
|
||||
""" Build javascript config for jSignature initialization.
|
||||
It's a dict with for which default values come from settings
|
||||
and can be overriden by jsignature_attrs, given at widget
|
||||
instanciation time """
|
||||
jsignature_config = JSIGNATURE_DEFAULT_CONFIG.copy()
|
||||
jsignature_config.update(self.jsignature_attrs)
|
||||
return jsignature_config
|
||||
|
||||
def build_jsignature_id(self, name):
|
||||
""" Build HTML id for jsignature container.
|
||||
It's important because it's used in javascript code """
|
||||
return 'jsign_%s' % name
|
||||
|
||||
def prep_value(self, value):
|
||||
""" Prepare value before effectively render widget """
|
||||
if value in JSIGNATURE_EMPTY_VALUES:
|
||||
return "[]"
|
||||
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, renderer=None):
|
||||
""" Render widget """
|
||||
# Build config
|
||||
jsign_id = self.build_jsignature_id(name)
|
||||
jsignature_config = self.build_jsignature_config()
|
||||
|
||||
# Prepare value
|
||||
value = self.prep_value(value)
|
||||
|
||||
# Build output
|
||||
hidden_input = super(JSignatureWidget, self).render(name, value, attrs)
|
||||
div = u'<div id="%s" class="jsign-container"></div>' % jsign_id
|
||||
clr = u'<input type="button" value="%s" class="btn">' % _('Reset')
|
||||
js = u'$("#%s").jSignature(%s);' % (jsign_id, json.dumps(jsignature_config))
|
||||
js += u'$("#%s").jSignature("setData", %s,"native");' % (jsign_id, json.dumps(value))
|
||||
js = u'<script type="text/javascript">%s</script>' % js
|
||||
out = u'<div class="jsign-wrapper">%s%s%s%s</div>' % (hidden_input, div, clr, js)
|
||||
context = {
|
||||
'hidden': super(JSignatureWidget, self).render(name, value, attrs),
|
||||
'jsign_id': jsign_id,
|
||||
'reset_btn_text': _('Reset'),
|
||||
'config': jsignature_config,
|
||||
'js_config': mark_safe(json.dumps(jsignature_config)),
|
||||
'value': mark_safe(value),
|
||||
}
|
||||
out = render_to_string('jsignature/widget.html', context)
|
||||
|
||||
return mark_safe(out)
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
Django >= 1.4
|
||||
pillow
|
||||
pyquery<1.4.2; python_version<="2.7"
|
||||
pyquery>=1.4.2; python_version>"2.7"
|
||||
|
||||
15
runtests.py
Executable file
15
runtests.py
Executable 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))
|
||||
BIN
screen.png
Normal file
BIN
screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
37
setup.py
37
setup.py
@@ -5,24 +5,37 @@ here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
setup(
|
||||
name='django-jsignature',
|
||||
version='0.7.5.dev0',
|
||||
version='0.11',
|
||||
author='Florent Lebreton',
|
||||
author_email='florent.lebreton@makina-corpus.com',
|
||||
url='https://github.com/makinacorpus/django-jsignature',
|
||||
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(),
|
||||
url='https://github.com/fle/django-jsignature',
|
||||
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'],
|
||||
packages=find_packages(),
|
||||
include_package_data = True,
|
||||
zip_safe = False,
|
||||
classifiers = ['Topic :: Utilities',
|
||||
install_requires=['Django>=1.11', 'pillow', 'pyquery>=1.4.2'],
|
||||
packages=find_packages(exclude=['example_project*', 'tests']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
classifiers=[
|
||||
'Topic :: Utilities',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'Intended Audience :: Developers',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: Django',
|
||||
'Development Status :: 4 - Beta',
|
||||
'Programming Language :: Python :: 2.7'],
|
||||
'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.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'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
0
tests/__init__.py
Normal file
6
tests/models.py
Normal file
6
tests/models.py
Normal file
@@ -0,0 +1,6 @@
|
||||
""" Provides a dummy model implementing JSignatureFieldsMixin """
|
||||
from jsignature.mixins import JSignatureFieldsMixin
|
||||
|
||||
|
||||
class JSignatureTestModel(JSignatureFieldsMixin):
|
||||
pass
|
||||
36
tests/settings.py
Normal file
36
tests/settings.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
|
||||
DEBUG = True
|
||||
|
||||
USE_TZ = 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,
|
||||
},
|
||||
]
|
||||
12
tests/test_faulty_signature.py
Normal file
12
tests/test_faulty_signature.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import json
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
from jsignature.templatetags.jsignature_filters import signature_base64
|
||||
|
||||
FAULTY_SIGNATURE = [{"x": [120.00675156801722, 116.11464070635179, 114.16858527551909], "y": [83.0316983821957, 76.54484694608666, 224.44505968937275]}, {"x": [161.52260075911508, 165.4147116207805, 173.84761848772226, 184.2265807854967, 201.0923945193802], "y": [84.97775381302841, 79.78827266414118, 72.00405094081033, 62.922458930257676, 56.43560749414864]}]
|
||||
DUMMY_STR_VALUE = json.dumps(FAULTY_SIGNATURE)
|
||||
|
||||
class TemplateFilterFailedTest(SimpleTestCase):
|
||||
def test_throw_error(self):
|
||||
output = signature_base64(DUMMY_STR_VALUE)
|
||||
68
tests/test_fields.py
Normal file
68
tests/test_fields.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import json
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
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):
|
||||
|
||||
def test_to_python_empty(self):
|
||||
f = JSignatureField()
|
||||
for val in ['', [], '[]']:
|
||||
self.assertIsNone(f.to_python(val))
|
||||
|
||||
def test_to_python_correct_value_python(self):
|
||||
f = JSignatureField()
|
||||
val = [{"x": [1, 2], "y": [3, 4]}]
|
||||
self.assertEqual(val, f.to_python(val))
|
||||
|
||||
def test_to_python_correct_value_json(self):
|
||||
f = JSignatureField()
|
||||
val = [{"x": [1, 2], "y": [3, 4]}]
|
||||
val_str = '[{"x":[1,2], "y":[3,4]}]'
|
||||
self.assertEqual(val, f.to_python(val_str))
|
||||
|
||||
def test_to_python_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 ['', [], '[]']:
|
||||
self.assertIsNone(f.get_prep_value(val))
|
||||
|
||||
def test_get_prep_value_correct_values_python(self):
|
||||
f = JSignatureField()
|
||||
val = [{"x": [1, 2], "y": [3, 4]}]
|
||||
val_prep = f.get_prep_value(val)
|
||||
self.assertIsInstance(val_prep, string_types)
|
||||
self.assertEqual(val, json.loads(val_prep))
|
||||
|
||||
def test_get_prep_value_correct_values_json(self):
|
||||
f = JSignatureField()
|
||||
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, string_types)
|
||||
self.assertEqual(val, json.loads(val_prep))
|
||||
|
||||
def test_get_prep_value_incorrect_values(self):
|
||||
f = JSignatureField()
|
||||
val = type('Foo')
|
||||
self.assertRaises(ValidationError, f.get_prep_value, val)
|
||||
|
||||
def test_formfield(self):
|
||||
f = JSignatureField()
|
||||
cls = f.formfield().__class__
|
||||
self.assertTrue(issubclass(cls, JSignatureFormField))
|
||||
19
tests/test_filter.py
Normal file
19
tests/test_filter.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import json
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
from jsignature.templatetags.jsignature_filters import signature_base64
|
||||
|
||||
DUMMY_VALUE = [{"x": [205, 210], "y": [59, 63]},
|
||||
{"x": [205, 207], "y": [67, 64]}]
|
||||
DUMMY_STR_VALUE = json.dumps(DUMMY_VALUE)
|
||||
|
||||
|
||||
class TemplateFilterTest(SimpleTestCase):
|
||||
def test_inputs_bad_type_value(self):
|
||||
self.assertEqual(signature_base64(object()), '')
|
||||
self.assertEqual(signature_base64(None), '')
|
||||
|
||||
def test_outputs_as_base64(self):
|
||||
output = signature_base64(DUMMY_STR_VALUE)
|
||||
self.assertEqual(output, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAABNCAYAAADNYApnAAABIklEQVR4nO3ZP0odURjG4d/ce5dgE3EHgXTp07iD6B7cga3YpUoh7iFrCti4BkGw0OLMoEVIEW4yxPs81ZxzGPial+/8KQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPh3ttW0dhFwCN4GbbNaFXAAlrCdVh/m791KtcC7tmkE7mP1XP2sPs1rQgd7NjVCd1TdNkL3UJ2tWRQciovqqRG8q0aXc6aDPZsat5RVX6q7Rui+zXPbX/wD/KFla7mc306qH9XneazLwZ4s3eu8eqwuV6wF3rXllvK4um9sI7/Oc7s8hMNeLVf/3xthu5nHzmzwF0y9drjrxvPAMgcA/7/lWUBnAwAAAAAAfucFwt4TZmdXW+AAAAAASUVORK5CYII=")
|
||||
27
tests/test_forms.py
Normal file
27
tests/test_forms.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from django.test import SimpleTestCase
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from jsignature.widgets import JSignatureWidget
|
||||
from jsignature.forms import JSignatureField
|
||||
|
||||
|
||||
class JSignatureFormFieldTest(SimpleTestCase):
|
||||
|
||||
def test_widget(self):
|
||||
f = JSignatureField()
|
||||
self.assertIsInstance(f.widget, JSignatureWidget)
|
||||
|
||||
def test_to_python_empty_values(self):
|
||||
f = JSignatureField()
|
||||
for val in ['', [], '[]']:
|
||||
self.assertIsNone(f.to_python(val))
|
||||
|
||||
def test_to_python_correct_values(self):
|
||||
f = JSignatureField()
|
||||
val = '[{"x":[1,2], "y":[3,4]}]'
|
||||
self.assertEqual([{'x': [1, 2], 'y': [3, 4]}], f.to_python(val))
|
||||
|
||||
def test_to_python_incorrect_values(self):
|
||||
f = JSignatureField()
|
||||
val = 'foo'
|
||||
self.assertRaises(ValidationError, f.to_python, val)
|
||||
47
tests/test_mixins.py
Normal file
47
tests/test_mixins.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from datetime import date
|
||||
from django.test import TestCase
|
||||
|
||||
from .models import JSignatureTestModel
|
||||
|
||||
|
||||
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]}]
|
||||
i = JSignatureTestModel(signature=signature_value)
|
||||
i.save()
|
||||
i = JSignatureTestModel.objects.get(pk=i.pk)
|
||||
self.assertEqual(date.today(), i.signature_date.date())
|
||||
|
||||
def test_save_no_change(self):
|
||||
# If signature doesn't change, signature date must not be updated
|
||||
signature_value = [{"x": [1, 2], "y": [3, 4]}]
|
||||
i = JSignatureTestModel(signature=signature_value)
|
||||
i.save()
|
||||
i.signature_date = date(2013, 1, 1)
|
||||
i.save()
|
||||
i = JSignatureTestModel.objects.get(pk=i.pk)
|
||||
self.assertEqual(date(2013, 1, 1), i.signature_date.date())
|
||||
|
||||
def test_save_change(self):
|
||||
# If signature changes, signature date must be updated too
|
||||
signature_value = [{"x": [1, 2], "y": [3, 4]}]
|
||||
new_signature_value = [{"x": [5, 6], "y": [7, 8]}]
|
||||
i = JSignatureTestModel(signature=signature_value,
|
||||
signature_date=date(2013, 1, 1))
|
||||
i.save()
|
||||
i.signature_date = date(2013, 1, 1)
|
||||
i.signature = new_signature_value
|
||||
i.save()
|
||||
i = JSignatureTestModel.objects.get(pk=i.pk)
|
||||
self.assertEqual(date.today(), i.signature_date.date())
|
||||
|
||||
def test_save_none(self):
|
||||
# If sinature is set to None, it must be the same for signature_date
|
||||
signature_value = [{"x": [1, 2], "y": [3, 4]}]
|
||||
i = JSignatureTestModel(signature=signature_value)
|
||||
i.save()
|
||||
i.signature = None
|
||||
i.save()
|
||||
i = JSignatureTestModel.objects.get(pk=i.pk)
|
||||
self.assertIsNone(i.signature_date)
|
||||
36
tests/test_utils.py
Normal file
36
tests/test_utils.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import json
|
||||
import os
|
||||
import imghdr
|
||||
from PIL import Image
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
from jsignature.utils import draw_signature
|
||||
|
||||
DUMMY_VALUE = [{"x": [205, 210], "y": [59, 63]},
|
||||
{"x": [205, 207], "y": [67, 64]}]
|
||||
DUMMY_STR_VALUE = json.dumps(DUMMY_VALUE)
|
||||
|
||||
|
||||
class UtilsTest(SimpleTestCase):
|
||||
|
||||
def test_inputs_bad_str_value(self):
|
||||
self.assertRaises(ValueError, draw_signature, 'foo_bar')
|
||||
|
||||
def test_inputs_bad_type_value(self):
|
||||
self.assertRaises(ValueError, draw_signature, object())
|
||||
|
||||
def test_inputs_good_list_value(self):
|
||||
draw_signature(DUMMY_VALUE)
|
||||
|
||||
def test_inputs_good_str_value(self):
|
||||
draw_signature(DUMMY_STR_VALUE)
|
||||
|
||||
def test_outputs_as_file(self):
|
||||
output = draw_signature(DUMMY_VALUE, as_file=True)
|
||||
self.assertTrue(os.path.isfile(output))
|
||||
self.assertIsNotNone(imghdr.what(output))
|
||||
|
||||
def test_outputs_as_image(self):
|
||||
output = draw_signature(DUMMY_VALUE)
|
||||
self.assertTrue(issubclass(output.__class__, Image.Image))
|
||||
self.assertTrue(all(output.getbbox()))
|
||||
102
tests/test_widgets.py
Normal file
102
tests/test_widgets.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import json
|
||||
from pyquery import PyQuery as pq
|
||||
|
||||
from django.test import SimpleTestCase, override_settings
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
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
|
||||
media_js = list(media.render_js())
|
||||
self.assertEqual(2, len(media_js))
|
||||
media_js_str = "".join(media_js)
|
||||
self.assertIn('jSignature.min.js', media_js_str)
|
||||
self.assertIn('django_jsignature.js', media_js_str)
|
||||
media_css = list(media.render_css())
|
||||
self.assertEqual([], 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))
|
||||
|
||||
@override_settings(JSIGNATURE_JQUERY='https://code.jquery.com/jquery-3.5.0.min.js')
|
||||
def test_media_custom_jquery(self):
|
||||
widget = JSignatureWidget()
|
||||
media = widget.media
|
||||
media_js = list(media.render_js())
|
||||
self.assertEqual(3, len(media_js))
|
||||
|
||||
def test_init(self):
|
||||
w = JSignatureWidget()
|
||||
self.assertEqual({}, w.jsignature_attrs)
|
||||
given_attrs = {'width': 300, 'height': 100}
|
||||
w = JSignatureWidget(jsignature_attrs=given_attrs)
|
||||
self.assertEqual(given_attrs, w.jsignature_attrs)
|
||||
|
||||
def test_build_jsignature_id(self):
|
||||
w = JSignatureWidget()
|
||||
id = w.build_jsignature_id('foo')
|
||||
self.assertEqual('jsign_foo', id)
|
||||
|
||||
def test_build_jsignature_config(self):
|
||||
w = JSignatureWidget(jsignature_attrs={'width': 400})
|
||||
config = w.build_jsignature_config()
|
||||
self.assertEqual(400, config.get('width'))
|
||||
self.assertEqual(JSIGNATURE_HEIGHT, config.get('height'))
|
||||
|
||||
def test_prep_value_empty_values(self):
|
||||
w = JSignatureWidget()
|
||||
for val in ['', [], '[]']:
|
||||
self.assertEqual('[]', w.prep_value(val))
|
||||
|
||||
def test_prep_value_correct_values_python(self):
|
||||
w = JSignatureWidget()
|
||||
val = [{"x": [1, 2], "y": [3, 4]}]
|
||||
val_prep = w.prep_value(val)
|
||||
self.assertIsInstance(val_prep, string_types)
|
||||
self.assertEqual(val, json.loads(val_prep))
|
||||
|
||||
def test_prep_value_correct_values_json(self):
|
||||
w = JSignatureWidget()
|
||||
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, string_types)
|
||||
self.assertEqual(val, json.loads(val_prep))
|
||||
|
||||
def test_prep_value_incorrect_values(self):
|
||||
w = JSignatureWidget()
|
||||
val = type('Foo')
|
||||
self.assertRaises(ValidationError, w.prep_value, val)
|
||||
|
||||
def test_render(self):
|
||||
w = JSignatureWidget()
|
||||
output = w.render(name='foo', value=None)
|
||||
# Almost useless :/
|
||||
self.assertEqual(1, len(pq('.jsign-wrapper', output)))
|
||||
self.assertEqual(1, len(pq('[type=hidden]', output)))
|
||||
|
||||
def test_render_reset_button_true(self):
|
||||
w = JSignatureWidget(jsignature_attrs={'ResetButton': True})
|
||||
output = w.render(name='foo', value=None)
|
||||
self.assertEqual(1, len(pq('[type=button]', output)))
|
||||
|
||||
def test_render_reset_button_false(self):
|
||||
w = JSignatureWidget(jsignature_attrs={'ResetButton': False})
|
||||
output = w.render(name='foo', value=None)
|
||||
self.assertEqual(0, len(pq('[type=button]', output)))
|
||||
25
tox.ini
Normal file
25
tox.ini
Normal file
@@ -0,0 +1,25 @@
|
||||
[tox]
|
||||
envlist =
|
||||
{py36,py37,py38,py39,py310}-django22,
|
||||
{py36,py37,py38,py39,py310}-django32,
|
||||
{py38,py39,py310}-django{40,master},
|
||||
py310-djangomaster
|
||||
|
||||
[testenv]
|
||||
deps=
|
||||
django22: Django>=2.2,<3.0
|
||||
django32: Django>=3.2,<4.0
|
||||
django40: Django>=4.0,<4.1
|
||||
djangomaster: https://github.com/django/django/archive/main.tar.gz
|
||||
-r requirements.txt
|
||||
coverage
|
||||
|
||||
commands= coverage run ./runtests.py
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.6: py36-django{22,32}
|
||||
3.7: py37-django{22,32}
|
||||
3.8: py38-django{22,32,40}
|
||||
3.9: py39-django{22,32,40}
|
||||
3.10: py310-django{22,32,40,master}
|
||||
Reference in New Issue
Block a user