diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..ee4d680 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,14 @@ +Copyright (c) 2012-2013 Makina Corpus + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..cd82c09 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include README.rst LICENSE +recursive-include jsignature/templates *.html +recursive-include jsignature/static *.js +recursive-include jsignature/static *.css diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..2ecf9a8 --- /dev/null +++ b/README.rst @@ -0,0 +1,11 @@ +================== +django-jsignature +================== + +A simple way to use jSignature jQuery plugin in your Django 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 + diff --git a/jsignature/__init__.py b/jsignature/__init__.py new file mode 100644 index 0000000..c2170fc --- /dev/null +++ b/jsignature/__init__.py @@ -0,0 +1,6 @@ +""" + django-jsignature provides a simple way to handle jSignature jQuery plugin + through a django form. + + See : https://github.com/brinley/jSignature/blob/master/README.md +""" diff --git a/jsignature/fields.py b/jsignature/fields.py new file mode 100644 index 0000000..ee99b26 --- /dev/null +++ b/jsignature/fields.py @@ -0,0 +1,54 @@ +""" + Provides a django model field to store a signature captured + 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 + +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' + + def to_python(self, value): + """ + Validates that the input can be red as a JSON object. Returns a Python + datetime.date object. + """ + if value in validators.EMPTY_VALUES: + return None + elif isinstance(value, list): + return value + try: + return json.loads(value) + except ValueError: + 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: + return None + elif isinstance(value, basestring): + return value + elif isinstance(value, list): + return json.dumps(value) + raise ValidationError('Invalid format.') + + def formfield(self, **kwargs): + 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"]) diff --git a/jsignature/forms.py b/jsignature/forms.py new file mode 100644 index 0000000..22243af --- /dev/null +++ b/jsignature/forms.py @@ -0,0 +1,27 @@ +""" + Provides a django form field to handle a signature capture field with + with jSignature jQuery plugin +""" +import json +from django.forms.fields import Field +from django.core import validators +from django.core.exceptions import ValidationError +from .widgets import JSignatureWidget + +class JSignatureField(Field): + """ + A field handling a signature capture field with with jSignature + """ + widget = JSignatureWidget() + + def to_python(self, value): + """ + Validates that the input can be red as a JSON object. Returns a Python + datetime.date object. + """ + if value in validators.EMPTY_VALUES: + return None + try: + return json.loads(value) + except ValueError: + raise ValidationError('Invalid JSON format.') diff --git a/jsignature/mixins.py b/jsignature/mixins.py new file mode 100644 index 0000000..87aeb4b --- /dev/null +++ b/jsignature/mixins.py @@ -0,0 +1,36 @@ +""" + A django mixin providing fields to store a signature captured + with jSignature jQuery plugin +""" +from datetime import datetime +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from .fields import JSignatureField + +class JSignatureFieldsMixin(models.Model): + """ Mixin class providing fields to store a signature with jSignature """ + signature = JSignatureField( + _('Signature'), + blank=True, + null=True) + signature_date = models.DateTimeField( + _('Signature date'), + blank=True, + null=True) + + class Meta: + abstract = True + + 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 + + print self.signature + if self.signature: + if is_new or self.signature != original.signature: + self.signature_date = datetime.now() + else: + self.signature_date = None + + super(JSignatureFieldsMixin, self).save() diff --git a/jsignature/models.py b/jsignature/models.py new file mode 100644 index 0000000..1b7fe20 --- /dev/null +++ b/jsignature/models.py @@ -0,0 +1 @@ +""" Simply turns this module in a django app """ diff --git a/jsignature/settings.py b/jsignature/settings.py new file mode 100644 index 0000000..a7fd226 --- /dev/null +++ b/jsignature/settings.py @@ -0,0 +1,19 @@ +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_DEFAULT_CONFIG = { + 'width': JSIGNATURE_WIDTH, + 'height': JSIGNATURE_HEIGHT, + 'color': JSIGNATURE_COLOR, + 'background-color': JSIGNATURE_BACKGROUND_COLOR, + 'decor-color': JSIGNATURE_DECOR_COLOR, + 'lineWidth': JSIGNATURE_LINE_WIDTH, + 'UndoButton': JSIGNATURE_UNDO_BUTTON, +} diff --git a/jsignature/static/js/django_jsignature.js b/jsignature/static/js/django_jsignature.js new file mode 100644 index 0000000..4989826 --- /dev/null +++ b/jsignature/static/js/django_jsignature.js @@ -0,0 +1,15 @@ +$(document).ready(function() { + + /* Each time user is done drawing a stroke, update value of hidden input */ + $(document).delegate(".jsign-container", "change", function(e) { + var jSignature_data = $(this).jSignature('getData', 'native'); + var django_field_name = $(this).attr('id').split('_')[1]; + $('#id_' + django_field_name).val(JSON.stringify(jSignature_data)); + }); + + /* Bind clear button */ + $(document).delegate(".jsign-wrapper input", "click", function(e) { + $(this).siblings('.jsign-container').jSignature('reset'); + }); + +}) diff --git a/jsignature/static/js/jSignature.min.js b/jsignature/static/js/jSignature.min.js new file mode 100644 index 0000000..0f60957 --- /dev/null +++ b/jsignature/static/js/jSignature.min.js @@ -0,0 +1,79 @@ +/* + +jSignature v2 "2013-08-26T07:00" "commit ID 986265299fecdb0f5b20acce92319220809707bf" +Copyright (c) 2012 Willow Systems Corp http://willow-systems.com +Copyright (c) 2010 Brinley Ang http://www.unbolt.net +MIT License + + +base64 encoder +MIT, GPL +http://phpjs.org/functions/base64_encode ++ original by: Tyler Akins (http://rumkin.com) ++ improved by: Bayron Guevara ++ improved by: Thunder.m ++ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) ++ bugfixed by: Pellentesque Malesuada ++ improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) ++ improved by: Rafal Kukawski (http://kukawski.pl) + + +jSignature v2 jSignature's Undo Button and undo functionality plugin + + +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').appendTo(d);this.isCanvasEmulator=!1;a=this.canvas=this.initializeCanvas(f);e=$(a);this.$controlbarLower=$('
').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&&0f.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;hthis.lineCurveThreshold){g=2this.lineCurveThreshold)if(1').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;-1g&&0a&&(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;hb?(b=2f){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',''],b,a=c.length,e,d=[],g=[],h=e=b=0,k=0,n=[];if(0!==a){for(b=0;bc?0:c;k=0>d?0:d;b=a-c;e=g-d}f.push('');b=0;for(a=n.length;b< +a;b++)e=n[b],f.push('');f.push("");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;ca&&(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;lm&&(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>18&63,a=d>>12&63,e=d>>6&63,d&=63,k[h++]=f[b]+f[a]+f[e]+f[d];while(g' % jsign_id + clr = u'' % _('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'' % js + out = u'
%s%s%s%s
' % (hidden_input, div, clr, js) + + return mark_safe(out) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c9d5053 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django >= 1.4 +pillow diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..82a8b2e --- /dev/null +++ b/setup.py @@ -0,0 +1,28 @@ +import os +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) + +setup( + name='django-jsignature', + version='0.7.5.dev0', + 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(), + license='LPGL, see LICENSE file.', + install_requires=['Django'], + packages=find_packages(), + 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'], +)