Improve data file handling for file batches.
Leverages a FormAlchemy "extension" of sorts.
This commit is contained in:
parent
2e8db05717
commit
3614254804
4 changed files with 103 additions and 41 deletions
|
@ -46,6 +46,7 @@ from rattail.threads import Thread
|
|||
from tailbone.db import Session
|
||||
from tailbone.views import SearchableAlchemyGridView, CrudView
|
||||
from tailbone.forms import DateTimeFieldRenderer, UserFieldRenderer, EnumFieldRenderer
|
||||
from tailbone.forms.renderers.batch import FileFieldRenderer
|
||||
from tailbone.grids.search import BooleanSearchFilter, EnumSearchFilter
|
||||
from tailbone.progress import SessionProgress
|
||||
|
||||
|
@ -436,6 +437,7 @@ class BatchCrud(BaseCrud):
|
|||
Redirect to view batch after creating a batch.
|
||||
"""
|
||||
batch = form.fieldset.model
|
||||
Session.flush()
|
||||
return self.view_url(batch.uuid)
|
||||
|
||||
def post_update_url(self, form):
|
||||
|
@ -561,28 +563,6 @@ class BatchCrud(BaseCrud):
|
|||
return HTTPFound(location=self.view_url(batch.uuid))
|
||||
|
||||
|
||||
class DownloadLinkRenderer(formalchemy.FieldRenderer):
|
||||
"""
|
||||
Field renderer for batch filenames, shows a link to download the file.
|
||||
"""
|
||||
|
||||
def __init__(self, route_prefix):
|
||||
self.route_prefix = route_prefix
|
||||
|
||||
def __call__(self, field):
|
||||
super(DownloadLinkRenderer, self).__init__(field)
|
||||
return self
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
filename = self.value
|
||||
if not filename:
|
||||
return ''
|
||||
batch = self.field.parent.model
|
||||
return link_to(filename, self.request.route_url(
|
||||
'{0}.download'.format(self.route_prefix),
|
||||
uuid=batch.uuid))
|
||||
|
||||
|
||||
class FileBatchCrud(BatchCrud):
|
||||
"""
|
||||
Base CRUD view for batches which involve a file upload as the first step.
|
||||
|
@ -597,6 +577,21 @@ class FileBatchCrud(BatchCrud):
|
|||
return HTTPFound(location=self.request.route_url(
|
||||
'{0}.refresh'.format(self.route_prefix), uuid=batch.uuid))
|
||||
|
||||
@property
|
||||
def upload_dir(self):
|
||||
"""
|
||||
The path to the root upload folder, to be used as the ``storage_path``
|
||||
argument for the file field renderer.
|
||||
"""
|
||||
uploads = os.path.join(
|
||||
self.request.rattail_config.require('rattail', 'batch.files'),
|
||||
'uploads')
|
||||
uploads = self.request.rattail_config.get(
|
||||
'tailbone', 'batch.uploads', default=uploads)
|
||||
if not os.path.exists(uploads):
|
||||
os.makedirs(uploads)
|
||||
return uploads
|
||||
|
||||
def fieldset(self, model):
|
||||
"""
|
||||
Creates the fieldset for the view. Derived classes should *not*
|
||||
|
@ -609,14 +604,11 @@ class FileBatchCrud(BatchCrud):
|
|||
fs.cognized_by.set(label="Cognized by", renderer=UserFieldRenderer)
|
||||
fs.executed.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
|
||||
fs.executed_by.set(label="Executed by", renderer=UserFieldRenderer)
|
||||
fs.append(formalchemy.Field('data_file'))
|
||||
fs.data_file.set(renderer=formalchemy.fields.FileFieldRenderer)
|
||||
fs.filename.set(renderer=DownloadLinkRenderer(self.route_prefix), readonly=True)
|
||||
fs.filename.set(renderer=FileFieldRenderer.new(self), label="Data File")
|
||||
self.configure_fieldset(fs)
|
||||
if self.creating:
|
||||
del fs.created
|
||||
del fs.created_by
|
||||
del fs.filename
|
||||
if 'cognized' in fs.render_fields:
|
||||
del fs.cognized
|
||||
if 'cognized_by' in fs.render_fields:
|
||||
|
@ -628,8 +620,8 @@ class FileBatchCrud(BatchCrud):
|
|||
if 'data_rows' in fs.render_fields:
|
||||
del fs.data_rows
|
||||
else:
|
||||
if 'data_file' in fs.render_fields:
|
||||
del fs.data_file
|
||||
if self.updating and 'filename' in fs.render_fields:
|
||||
fs.filename.set(readonly=True)
|
||||
batch = fs.model
|
||||
if not batch.executed:
|
||||
if 'executed' in fs.render_fields:
|
||||
|
@ -648,7 +640,6 @@ class FileBatchCrud(BatchCrud):
|
|||
include=[
|
||||
fs.created,
|
||||
fs.created_by,
|
||||
fs.data_file,
|
||||
fs.filename,
|
||||
# fs.cognized,
|
||||
# fs.cognized_by,
|
||||
|
@ -669,13 +660,23 @@ class FileBatchCrud(BatchCrud):
|
|||
# For new batches, assign current user as creator, save file etc.
|
||||
if self.creating:
|
||||
batch.created_by = self.request.user
|
||||
batch.filename = form.fieldset.data_file.renderer._filename
|
||||
# Expunge batch from session to prevent it from being flushed.
|
||||
|
||||
# Expunge batch from session to prevent it from being flushed
|
||||
# during init. This is done as a convenience to views which
|
||||
# provide an init method. Some batches may have required fields
|
||||
# which aren't filled in yet, but the view may need to query the
|
||||
# database to obtain the values. This will cause a session flush,
|
||||
# and the missing fields will trigger data integrity errors.
|
||||
Session.expunge(batch)
|
||||
self.batch_inited = self.init_batch(batch)
|
||||
if self.batch_inited:
|
||||
Session.add(batch)
|
||||
batch.write_file(self.request.rattail_config, form.fieldset.data_file.value)
|
||||
Session.flush()
|
||||
|
||||
# Handler saves a copy of the file and updates the batch filename.
|
||||
path = os.path.join(self.upload_dir, batch.filename)
|
||||
self.handler.set_data_file(batch, path)
|
||||
os.remove(path)
|
||||
|
||||
def init_batch(self, batch):
|
||||
"""
|
||||
|
@ -716,12 +717,11 @@ class FileBatchCrud(BatchCrud):
|
|||
batch = self.current_batch()
|
||||
if not batch:
|
||||
return HTTPNotFound()
|
||||
config = self.request.rattail_config
|
||||
path = batch.filepath(config)
|
||||
path = self.handler.data_path(batch)
|
||||
response = FileResponse(path, request=self.request)
|
||||
response.headers[b'Content-Length'] = str(batch.filesize(config))
|
||||
response.headers[b'Content-Disposition'] = b'attachment; filename="{0}"'.format(
|
||||
batch.filename.encode('ascii', 'replace'))
|
||||
response.headers[b'Content-Length'] = str(os.path.getsize(path))
|
||||
filename = os.path.basename(batch.filename).encode('ascii', 'replace')
|
||||
response.headers[b'Content-Disposition'] = b'attachment; filename="{0}"'.format(filename)
|
||||
return response
|
||||
|
||||
|
||||
|
|
3
tailbone/views/vendors/catalogs.py
vendored
3
tailbone/views/vendors/catalogs.py
vendored
|
@ -114,10 +114,9 @@ class VendorCatalogCrud(FileBatchCrud):
|
|||
fs.configure(
|
||||
include=[
|
||||
fs.vendor,
|
||||
fs.data_file.label("Catalog File"),
|
||||
fs.filename.label("Catalog File"),
|
||||
fs.parser_key.label("File Type"),
|
||||
fs.effective,
|
||||
fs.filename,
|
||||
fs.created,
|
||||
fs.created_by,
|
||||
fs.executed,
|
||||
|
|
3
tailbone/views/vendors/invoices.py
vendored
3
tailbone/views/vendors/invoices.py
vendored
|
@ -114,9 +114,8 @@ class VendorInvoiceCrud(FileBatchCrud):
|
|||
fs.configure(
|
||||
include=[
|
||||
fs.vendor.readonly(),
|
||||
fs.data_file.label("Invoice File"),
|
||||
fs.filename.label("Invoice File"),
|
||||
fs.parser_key.label("File Type"),
|
||||
fs.filename,
|
||||
fs.purchase_order_number.label(self.handler.po_number_title),
|
||||
fs.invoice_date.readonly(),
|
||||
fs.created,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue