6 Commits
0.9 ... 0.10

Author SHA1 Message Date
Sebastien Corbin
a740767a06 Release 0.10 2020-07-26 15:58:33 +02:00
Sebastien Corbin
ea249473f1 Add full example of front-office usage 2020-07-26 15:58:12 +02:00
Sebastien Corbin
bea517d746 Add a template filter for displaying signature as base64 2020-07-26 15:55:51 +02:00
Sebastien Corbin
4dd9e5f25f Improve coverage 2020-04-18 21:08:09 +02:00
Sebastien Corbin
72e9f3d6da Fix setup.py classifiers 2020-04-18 21:04:30 +02:00
Sebastien Corbin
7601258ea8 Improve setup.py for next time 2020-04-18 21:01:55 +02:00
13 changed files with 238 additions and 50 deletions

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ htmlcov
*.egg-info *.egg-info
.tox .tox
example.db example.db
/dist
/build

View File

@@ -2,6 +2,15 @@
CHANGELOG CHANGELOG
========= =========
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) 0.9 (2020-04-18)
================== ==================

View File

@@ -5,6 +5,7 @@ It provides:
* A form field and a form widget to handle jquery plugin through a Django form; * A form field and a form widget to handle jquery plugin through a Django form;
* A model field to store a captured signature; * 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 .. image:: https://img.shields.io/pypi/v/django-jsignature.svg
:target: https://pypi.python.org/pypi/django-jsignature/ :target: https://pypi.python.org/pypi/django-jsignature/
@@ -21,17 +22,15 @@ It provides:
.. image:: https://github.com/fle/django-jsignature/blob/master/screen.png .. image:: https://github.com/fle/django-jsignature/blob/master/screen.png
================== ==================
INSTALL Installation
================== ==================
For now:
:: ::
pip install django-jsignature pip install django-jsignature
================== ==================
USAGE Usage
================== ==================
* Add ``jsignature`` to your ``INSTALLED_APPS``: * Add ``jsignature`` to your ``INSTALLED_APPS``:
@@ -44,48 +43,37 @@ USAGE
'jsignature', 'jsignature',
) )
* Use provided form field and widget: * Use provided model field (for easy storage):
:: ::
# forms.py # models.py
from django import forms from django.db import models
from jsignature.forms import JSignatureField from jsignature.fields import JSignatureField
class SignatureForm(forms.Form): class SignatureModel(models.Model):
signature = JSignatureField() signature = JSignatureField()
* In your template * In your form template
:: ::
{{ form.media }} {{ form.media }}
<form action="." method="POST"> <form action="" method="post">
{% for field in form %} {{ form }}
{{ field.label_tag }} <input type="submit" value="Save" />
{{ field }}
{% endfor %}
<input type="submit" value="Save"/>
{% csrf_token %} {% csrf_token %}
</form> </form>
* Render image after form validation: * Render image from db value in your display template:
:: ::
# views.py {# yourtemplate.html #}
from jsignature.utils import draw_signature {% load jsignature_filters %}
from myapp.forms import SignatureForm
<img src="{{ obj.signature|signature_base64 }}" alt="{{ obj }}" />
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)
* By default, jSignature is made to work outside of admin, requiring that * By default, jSignature is made to work outside of admin, requiring that
you include the jQuery library in your ``<head>``. you include the jQuery library in your ``<head>``.
@@ -94,8 +82,11 @@ USAGE
``JSIGNATURE_JQUERY`` setting to ``admin``. Otherwise if set to any url ``JSIGNATURE_JQUERY`` setting to ``admin``. Otherwise if set to any url
pointing to jQuery, it will be automatically included. 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 Customization
================== ==================
JSignature plugin options are available in python: JSignature plugin options are available in python:
@@ -130,10 +121,11 @@ Available settings are:
* ``JSIGNATURE_RESET_BUTTON`` (ResetButton) * ``JSIGNATURE_RESET_BUTTON`` (ResetButton)
================== ==================
IN YOUR MODELS In your models
================== ==================
If you want to store signatures, provided mixin gives a ``signature`` and a ``signature_date`` that update themselves: If you want to store signatures easily, a provided mixin gives a ``signature``
and a ``signature_date`` that update themselves:
:: ::
@@ -144,6 +136,41 @@ If you want to store signatures, provided mixin gives a ``signature`` and a ``si
name = models.CharField() 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 Example project
================== ==================
@@ -162,16 +189,17 @@ If you want to have a demo of this package, just use the example project:
./manage.py migrate ./manage.py migrate
./manage.py createsuperuser ./manage.py createsuperuser
Fill the user info, launch django with ``./manage.py runserver`` and head over to Fill the user info, launch django with ``./manage.py runserver`` and head over
`http://127.0.0.1:8000/ <http://127.0.0.1:8000/>`_ and login with the to `http://127.0.0.1:8000/ <http://127.0.0.1:8000/>`_, you can also
credentials your provided. `login to the admin <http://127.0.0.1:8000/admin>`_ with the credentials your
provided.
================== ==================
AUTHORS Authors
================== ==================
* Florent Lebreton <florent.lebreton@makina-corpus.com> * Florent Lebreton <florent.lebreton@makina-corpus.com> (original author)
* Sébastien Corbin <sebastien.corbin@makina-corpus.com> * Sébastien Corbin <sebastien.corbin@makina-corpus.com> (maintainer)
|makinacom|_ |makinacom|_

View 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>

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -3,6 +3,11 @@ from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path
from example_project import views
urlpatterns = [ urlpatterns = [
path('', admin.site.urls), 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
View 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

View File

View 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'))
)

View File

@@ -5,7 +5,7 @@ here = os.path.abspath(os.path.dirname(__file__))
setup( setup(
name='django-jsignature', name='django-jsignature',
version='0.9', version='0.10',
author='Florent Lebreton', author='Florent Lebreton',
author_email='florent.lebreton@makina-corpus.com', author_email='florent.lebreton@makina-corpus.com',
url='https://github.com/fle/django-jsignature', url='https://github.com/fle/django-jsignature',
@@ -14,8 +14,8 @@ setup(
long_description=open(os.path.join(here, 'README.rst')).read() + '\n\n' + long_description=open(os.path.join(here, 'README.rst')).read() + '\n\n' +
open(os.path.join(here, 'CHANGES')).read(), open(os.path.join(here, 'CHANGES')).read(),
license='LPGL, see LICENSE file.', license='LPGL, see LICENSE file.',
install_requires=['Django', 'pillow'], install_requires=['Django>=1.11', 'pillow'],
packages=find_packages(), packages=find_packages(exclude=['example_project*', 'tests']),
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
classifiers=[ classifiers=[
@@ -29,13 +29,13 @@ setup(
'Framework :: Django :: 2.2', 'Framework :: Django :: 2.2',
'Framework :: Django :: 3.0', 'Framework :: Django :: 3.0',
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5' 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6' 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7' 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8' 'Programming Language :: Python :: 3.8',
], ],
) )

19
tests/test_filter.py Normal file
View 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=")

View File

@@ -34,6 +34,13 @@ class JSignatureWidgetTest(SimpleTestCase):
media_js = list(media.render_js()) media_js = list(media.render_js())
self.assertEqual(4, len(media_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): def test_init(self):
w = JSignatureWidget() w = JSignatureWidget()
self.assertEquals({}, w.jsignature_attrs) self.assertEquals({}, w.jsignature_attrs)