From eea34bdfaa325b43a98ccbf592acf1092a9f6325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Krienb=C3=BChl?= Date: Wed, 3 Jun 2015 15:38:08 +0200 Subject: [PATCH] Stores a checksum with each form definition and submission. --- HISTORY.rst | 3 +++ onegov/form/models.py | 22 ++++++++++++++++++++++ onegov/form/tests/test_collection.py | 11 ++++++++++- setup.py | 2 ++ 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 2323817..6cc951b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,9 @@ Changelog Unreleased ~~~~~~~~~~ +- Stores a checksum with each form definition and submission. + [href] + - Adds the ability to filter out submissions older than one hour. [href] diff --git a/onegov/form/models.py b/onegov/form/models.py index 6e713cc..cd28812 100644 --- a/onegov/form/models.py +++ b/onegov/form/models.py @@ -1,3 +1,4 @@ +from hashlib import md5 from onegov.core.orm import Base from onegov.core.orm.mixins import TimestampMixin from onegov.core.orm.types import JSON, UUID @@ -8,9 +9,14 @@ deferred, relationship, ) +from sqlalchemy_utils import observes from uuid import uuid4 +def hash_definition(definition): + return md5(definition.encode('utf-8')).hexdigest() + + class FormDefinition(Base, TimestampMixin): """ Defines a form stored in the database. """ @@ -31,6 +37,10 @@ class FormDefinition(Base, TimestampMixin): #: the form as parsable string definition = Column(Text, nullable=False) + #: the checksum of the definition, forms and submissions with matching + #: checksums are guaranteed to have the exact same definition + checksum = Column(Text, nullable=False) + #: the type of the form, this can be used to create custom polymorphic #: subclasses. See ``_. @@ -49,6 +59,10 @@ def form_class(self): return parse_form(self.definition) + @observes('definition') + def definition_observer(self, definition): + self.checksum = hash_definition(definition) + class FormSubmission(Base, TimestampMixin): """ Defines a submitted form in the database. """ @@ -66,6 +80,10 @@ class FormSubmission(Base, TimestampMixin): #: want to keep the old form around just in case. definition = Column(Text, nullable=False) + #: the checksum of the definition, forms and submissions with matching + #: checksums are guaranteed to have the exact same definition + checksum = Column(Text, nullable=False) + #: the submission data data = Column(JSON, nullable=False) @@ -92,6 +110,10 @@ def form_class(self): return parse_form(self.definition) + @observes('definition') + def definition_observer(self, definition): + self.checksum = hash_definition(definition) + def complete(self): """ Changes the state to 'complete', if the data is valid. """ diff --git a/onegov/form/tests/test_collection.py b/onegov/form/tests/test_collection.py index 961a926..36ae2eb 100644 --- a/onegov/form/tests/test_collection.py +++ b/onegov/form/tests/test_collection.py @@ -4,7 +4,7 @@ from delorean import Delorean from onegov.core.compat import BytesIO from onegov.form import FormCollection, PendingFormSubmission -from onegov.form.models import FormSubmissionFile +from onegov.form.models import FormSubmissionFile, hash_definition from onegov.form.errors import UnableToComplete from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.exc import FlushError @@ -14,6 +14,10 @@ from wtforms.csrf.core import CSRF +def test_form_checksum(): + assert hash_definition('abc') == '900150983cd24fb0d6963f7d28e17f72' + + def test_add_form(session): collection = FormCollection(session) @@ -36,6 +40,8 @@ def test_add_form(session): Last Name * = ___ """)) + assert form.checksum and form.checksum == hash_definition(form.definition) + def test_submit_form(session): collection = FormCollection(session) @@ -58,6 +64,9 @@ def test_submit_form(session): form = collection.definitions.by_name('tps-report') submission = form.submissions[0] + assert submission.checksum and submission.checksum == hash_definition( + form.definition) == hash_definition(submission.definition) + stored_form = submission.form_class(data=submission.data) assert submitted_form.data == stored_form.data diff --git a/setup.py b/setup.py index 525248d..a7f37a4 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ def get_long_description(): zip_safe=False, platforms='any', install_requires=[ + 'delorean', 'humanize', 'jsonpickle', 'onegov.core>=0.3.5', @@ -41,6 +42,7 @@ def get_long_description(): 'pyyaml', 'python-magic', 'python-stdnum', + 'sqlalchemy_utils', 'wtforms', 'wtforms-components[color]', 'unidecode'