Add validtion to prevent duplicate files for multi-invoice receiving

by comparing sha256 hash values for each file
This commit is contained in:
Lance Edgar 2023-10-19 14:03:25 -05:00
parent aaf6f05820
commit 0d30247353
3 changed files with 36 additions and 1 deletions

View file

@ -24,6 +24,7 @@
Forms Core Forms Core
""" """
import hashlib
import json import json
import logging import logging
import warnings import warnings
@ -659,11 +660,25 @@ class Form(object):
'widget': MultiFileUploadWidget(tmpstore)} 'widget': MultiFileUploadWidget(tmpstore)}
# if 'required' in kwargs and not kwargs['required']: # if 'required' in kwargs and not kwargs['required']:
# kw['missing'] = colander.null # kw['missing'] = colander.null
if kwargs.get('validate_unique'):
kw['validator'] = self.validate_multiple_files_unique
files_node = colander.SequenceSchema(file_node, **kw) files_node = colander.SequenceSchema(file_node, **kw)
self.set_node(key, files_node) self.set_node(key, files_node)
else: else:
raise ValueError("unknown type for '{}' field: {}".format(key, type_)) raise ValueError("unknown type for '{}' field: {}".format(key, type_))
def validate_multiple_files_unique(self, node, value):
# get SHA256 hash for each file; error if duplicates encountered
hashes = {}
for fileinfo in value:
fp = fileinfo['fp']
fp.seek(0)
filehash = hashlib.sha256(fp.read()).hexdigest()
if filehash in hashes:
node.raise_invalid(f"Duplicate file detected: {fileinfo['filename']}")
hashes[filehash] = fileinfo
def set_enum(self, key, enum, empty=None): def set_enum(self, key, enum, empty=None):
if enum: if enum:
self.enums[key] = enum self.enums[key] = enum
@ -906,6 +921,11 @@ class Form(object):
return json.dumps({'name': value['filename']}) return json.dumps({'name': value['filename']})
return 'null' return 'null'
elif isinstance(value, list) and all([isinstance(f, dfwidget.filedict)
for f in value]):
return json.dumps([{'name': f['filename']}
for f in value])
app = self.request.rattail_config.get_app() app = self.request.rattail_config.get_app()
value = app.json_friendly(value) value = app.json_friendly(value)
return json.dumps(value) return json.dumps(value)

View file

@ -323,6 +323,21 @@ class MultiFileUploadWidget(dfwidget.FileUploadWidget):
template = 'multi_file_upload' template = 'multi_file_upload'
requirements = () requirements = ()
def serialize(self, field, cstruct, **kw):
if cstruct in (colander.null, None):
cstruct = []
if cstruct:
for fileinfo in cstruct:
uid = fileinfo['uid']
if uid not in self.tmpstore:
self.tmpstore[uid] = fileinfo
readonly = kw.get("readonly", self.readonly)
template = readonly and self.readonly_template or self.template
values = self.get_template_values(field, cstruct, kw)
return field.renderer(template, **values)
def deserialize(self, field, pstruct): def deserialize(self, field, pstruct):
if pstruct is colander.null: if pstruct is colander.null:
return colander.null return colander.null

View file

@ -570,7 +570,7 @@ class ReceivingBatchView(PurchasingBatchView):
elif workflow == 'from_multi_invoice': elif workflow == 'from_multi_invoice':
if 'invoice_files' not in f: if 'invoice_files' not in f:
f.insert_before('invoice_file', 'invoice_files') f.insert_before('invoice_file', 'invoice_files')
f.set_type('invoice_files', 'multi_file') f.set_type('invoice_files', 'multi_file', validate_unique=True)
f.set_required('invoice_parser_key') f.set_required('invoice_parser_key')
f.remove('truck_dump_batch_uuid', f.remove('truck_dump_batch_uuid',
'po_number', 'po_number',