A multi submission importer for easyform

A Multi-submission
importer for EasyForm
Annette Lewis
Developer @ Six Feet Up, Inc.
annette@sixfeetup.com
2
The Setup
✖ Created several lengthy forms to register
attendees for an event
✖ Needed a way to mass import attendee
registrations
✖ Allow non Site Admins to prepare import
data
3
The Solution should:
✖ Work with most forms
✖ Provide a template to be filled out
✖ Allow Site Admins to preview data before
submitting import
✖ Execute form actions
4
Let's see this
in action!
The REsulting Solution
5
What I know about EasyForm
✖ EasyForm uses Dexterity
✖ Dexterity uses the z3c.form library to
build its forms, via the plone.z3cform
integration package
✖ Dexterity also relies on plone.autoform,
in particular its AutoExtensibleForm base
class
6
Generating the CSV
7
CSV output?
8
9
<browser:page
name="download-form-csv"
for="collective.easyform.interfaces.IEasyForm"
class="addon.policy.browser.views.GetCSVTemplateView"
permission="cmf.ModifyPortalContent"
layer="addon.policy.interfaces.IAddonPolicyLayer"
/>
<h2>Get csv template</h2>
<p>Use this file as a template for submitting your information.</p>
<form method="post" tal:attributes="action string:@@download-form-csv">
<input tal:replace="structure context/@@authenticator/authenticator" />
<input type="submit" value="Download CSV template" />
</form>
<br/>
Browser view and Download Button
10from collective.easyform.api import get_schema
from collective.easyform.api import getFieldsInOrder
from six import StringIO
import csv
from DateTime import DateTime
class GetCSVTemplateView(BrowserView):
def __call__(self, *args, **kwargs):
# download csv template for all form fields
form = self.context
response = self.request.RESPONSE
schema = get_schema(form)
fields = getFieldsInOrder(schema)
fieldnames = []
for f in fields:
fieldnames.append(f[0])
now = DateTime().ISO().replace(" ", "-").replace(":", "")
self.request.RESPONSE.setHeader(
"Content-type",
"text/comma-separated-values")
self.request.RESPONSE.setHeader(
"Content-Disposition",
'attachment; filename="{0}_{1}.csv"'.format(form.__name__, now),
)
csv_file = StringIO()
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
writer.writeheader()
return csv_file.getvalue()
Sample CSV output
11
Upload the Import
12
CSV output?
13
14
<browser:page
name="preview-csv-import"
for="collective.easyform.interfaces.IEasyForm"
class="addon.policy.browser.views.PreviewCsvDataView"
permission="cmf.ModifyPortalContent"
layer="addon.policy.interfaces.IAddonPolicyLayer"
/>
<h2>Import csv</h2>
<p>Upload the csv template here.</p>
<form action="@@preview-csv-import" method="post" enctype="multipart/form-data">
<input tal:replace="structure context/@@authenticator/authenticator" />
<input type=file name="file_attachment"><br>
<input type=submit value="Import csv data">
</form>
</div>
Import Button and Browser View
Reuse & REcycle
Reuse as much existing code as possible and
only add (override) what I need to
15
16
class CrudForm(AbstractCrudForm, form.Form):
template = viewpagetemplatefile.ViewPageTemplateFile('crud-master.pt')
description = u''
editform_factory = EditForm
addform_factory = AddForm
def update(self):
super(CrudForm, self).update()
addform = self.addform_factory(self, self.request)
editform = self.editform_factory(self, self.request)
addform.update()
editform.update()
self.subforms = [editform, addform]
from plone.z3cform.crud import crud
class SavedDataForm(crud.CrudForm):
template = ViewPageTemplateFile("saveddata_form.pt")
addform_factory = crud.NullForm
Down the rabbit hole
plone.z3cform/crud.py
collective.easyform/browser/actions.py
REview the Import
17
18
19
from collective.easyform.browser.actions import SavedDataForm
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
class csvimportcrud(SavedDataForm):
""" Use the SavedData Adapter form to review and validate csv import data
"""
template = ViewPageTemplateFile("templates/review_import.pt")
editform_factory = ImporterEditForm
from plone.z3cform import layout
PreviewCsvDataView = layout.wrap_form(csvimportcrud)
Building the Import Preview
<div class="crud-form"
tal:repeat="form view/subforms"
tal:content="structure form/render"
>
</div>
20
def get_items():
"""Subclasses must a list of all items to edit.
This list contains tuples of the form ``(id, item)``, where
the id is a unique identifiers to the items. The items must
be adaptable to the schema returned by ``update_schema`` and
``view_schema`` methods.
"""
class SavedDataForm(crud.CrudForm):
[...]
def get_items(self):
return [
(key, DataWrapper(key, value, self.context))
for key, value in self.field.getSavedFormInputItems()
]
Formatting my import Data
plone.z3cform/crud.py
collective.easyform/browser/actions.py
To the Code
21
22class csvimportcrud(SavedDataForm):
[...]
def importCSV(self, *args, **kwargs):
"""Import CSV information """
form = self.context
request = self.request
attachment = request.form['file_attachment']
data = self.getCSVdata(attachment, form)
return data
def getCSVdata(self, attachment, form):
# get form fields
schema = get_schema(form)
fields = getFieldsInOrder(schema)
fieldnames = []
for f in fields:
fieldnames.append(f[0])
# open csv file
if hasattr(attachment, 'file'):
data = attachment.file.read()
data = data.decode('UTF-8')
file = data.splitlines()
reader = csv.DictReader(file)
rows = list(reader)
# # format to match SavedDataForm expects
formatted_rows = []
for row in rows:
unsorted_data = dict(row)
unsorted_data = self.cleanup_values(form, row)
formatted_rows.append(unsorted_data)
return [(count, dict(formatted_rows))
for count, formatted_rows in enumerate(formatted_rows, 1)]
23
def migrate_saved_data(ploneformgen, easyform):
[...]
for key, value in zip(cols, row):
field = schema.get(key)
value = value.decode('utf8')
if IFromUnicode.providedBy(field):
value = field.fromUnicode(value)
elif IDatetime.providedBy(field) and value:
value = DateTime(value).asdatetime()
elif IDate.providedBy(field) and value:
value = DateTime(value).asdatetime().date()
elif ISet.providedBy(field):
try:
value = set(literal_eval(value))
except ValueError:
pass
elif INamedBlobFileField.providedBy(field):
value = None
data[key] = value
Clean-up values
Inspired by collective.easyform/migration/data.py - migrate_saved_data
collective.easyform/api.py
24
def cleanup(value):
"""Accepts lists, tuples or comma/semicolon-separated strings
and returns a list of native strings.
"""
if isinstance(value, six.string_types):
value = safe_unicode(value).strip()
value = value.replace(u",", u"n").replace(u";", u"n")
value = [s for s in value.splitlines()]
if isinstance(value, (list, tuple)):
value = [safe_unicode(s).strip() for s in value]
if six.PY2:
# py2 expects a list of bytes
value = [s.encode("utf-8") for s in value if s]
return value
Clean-up values pt 2
Also take advantage of easyform's cleanup()
collective.easyform/api.py
25
def get_items(self):
""" Get csv data and format """
data = []
if hasattr(self.request, 'file_attachment'):
if self.request.form.get('file_attachment'):
data = self.importCSV()
return data
elif hasattr(self.request, 'crud-edit.form.buttons.edit'):
# get data from submitted form
if self.request.form.get('data_export'):
data_str = self.request.data_export
# ast cannot handle sets
data = ast.literal_eval(data_str)
else:
data = []
url = "{0}/@@import-forms".format(
self.context.absolute_url()
)
IStatusMessage(self.request).add(
_(u'Error: Missing file attachment.'))
self.request.response.redirect(url)
return data
The new get_items
REview the Import
26
Pt 2 - Handling the Data
27
from collective.easyform.browser.actions import SavedDataForm
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
class csvimportcrud(SavedDataForm):
""" Use the SavedData Adapter form to review and validate csv import data
"""
template = ViewPageTemplateFile("templates/review_import.pt")
editform_factory = ImporterEditForm
class ImporterEditForm(crud.EditForm):
template = ViewPageTemplateFile('templates/importer-crud-table.pt')
Displaying the Import Data
<input name="data_export" type="hidden" value="" tal:attributes="value view/getData|nothing">
To the Code
28
SUbmit the Data
29
30
@button.buttonAndHandler(
PMF(u"Submit"), name="submit", condition=lambda form: not form.thanksPage
)
def handleSubmit(self, action):
unsorted_data, errors = self.extractData()
if errors:
self.status = self.formErrorsMessage
return
unsorted_data = self.updateServerSideData(unsorted_data)
errors = self.processActions(unsorted_data)
if errors:
return self.setErrorsMessage(errors)
data = OrderedDict(
[x for x in getFieldsInOrder(self.schema) if x[0] in unsorted_data]
)
data.update(unsorted_data)
[...]
EasyFOrm handleSubmit()
31
class ImporterEditForm(crud.EditForm):
[...]
@button.buttonAndHandler(_('Import form Data'),
name='edit',
condition=lambda form: form.context.update_schema)
def handle_edit(self, action):
success = _(u"Successfully updated")
partly_success = _(u"Some of your changes could not be applied.")
status = no_changes = _(u"No changes made.")
for subform in self.subforms:
data, errors = subform.extractData()
if errors:
[...]
imported_rows = 0
form = self.context.context
rows = self.subforms
total_rows = len(rows)
for x in rows:
row = data
try:
csvimportcrud(self.context,
self.request).submitForm(form, row)
imported_rows += 1
except IndexError:
continue
# self.status = status
IStatusMessage(self.request).add(
_(u'Imported {0} out of {1} row(s).').format(imported_rows,
total_rows))
My entry point
32
from collective.easyform.browser.view import EasyFormForm as easyformview
class csvimportcrud(SavedDataForm):
""" Use the SavedData Adapter form to review and validate csv import data
"""
def submitForm(self, form, row):
""" Process Easyform actions """
unsorted_data = row
errors = easyformview.processActions(self.context, unsorted_data)
if errors:
return form.setErrorsMessage(errors)
Submit the Subforms
The beginning?
A client request that taught me a lot and has
a pretty cool result.
33
Thanks For coming!!
Any questions?
You can find me at:
annette@sixfeetup.com
34
1 of 34

Recommended

WordPress - Custom Page Settings + Salesforce API Integration by
WordPress - Custom Page Settings + Salesforce API IntegrationWordPress - Custom Page Settings + Salesforce API Integration
WordPress - Custom Page Settings + Salesforce API IntegrationKhoi Nguyen
212 views15 slides
Collection View by
Collection ViewCollection View
Collection ViewHangyeol Lee
342 views119 slides
201104 iphone navigation-based apps by
201104 iphone navigation-based apps201104 iphone navigation-based apps
201104 iphone navigation-based appsJavier Gonzalez-Sanchez
1.2K views41 slides
How to Import data into OpenERP V7 by
How to Import data into OpenERP V7How to Import data into OpenERP V7
How to Import data into OpenERP V7Audaxis
38K views14 slides
Magento 2 | Declarative schema by
Magento 2 | Declarative schemaMagento 2 | Declarative schema
Magento 2 | Declarative schemaKiel Pykett
104 views18 slides
I os 11 by
I os 11I os 11
I os 11信嘉 陳
1K views59 slides

More Related Content

What's hot

VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法 by
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法Kenji Tanaka
39K views83 slides
Best practices on how to import data into OpenERP. Cyril Morisse, Audaxis by
Best practices on how to import data into OpenERP. Cyril Morisse, AudaxisBest practices on how to import data into OpenERP. Cyril Morisse, Audaxis
Best practices on how to import data into OpenERP. Cyril Morisse, AudaxisOdoo
31.5K views14 slides
Odoo - Backend modules in v8 by
Odoo - Backend modules in v8Odoo - Backend modules in v8
Odoo - Backend modules in v8Odoo
29.5K views23 slides
Taming forms with React by
Taming forms with ReactTaming forms with React
Taming forms with ReactGreeceJS
211 views75 slides
描画とビジネスをクリーンに分ける(公開用) by
描画とビジネスをクリーンに分ける(公開用)描画とビジネスをクリーンに分ける(公開用)
描画とビジネスをクリーンに分ける(公開用)Kenji Tanaka
1.3K views43 slides
Awesome State Management for React and Other Virtual-Dom Libraries by
Awesome State Management for React and Other Virtual-Dom LibrariesAwesome State Management for React and Other Virtual-Dom Libraries
Awesome State Management for React and Other Virtual-Dom LibrariesFITC
545 views53 slides

What's hot(6)

VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法 by Kenji Tanaka
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
Kenji Tanaka39K views
Best practices on how to import data into OpenERP. Cyril Morisse, Audaxis by Odoo
Best practices on how to import data into OpenERP. Cyril Morisse, AudaxisBest practices on how to import data into OpenERP. Cyril Morisse, Audaxis
Best practices on how to import data into OpenERP. Cyril Morisse, Audaxis
Odoo31.5K views
Odoo - Backend modules in v8 by Odoo
Odoo - Backend modules in v8Odoo - Backend modules in v8
Odoo - Backend modules in v8
Odoo29.5K views
Taming forms with React by GreeceJS
Taming forms with ReactTaming forms with React
Taming forms with React
GreeceJS211 views
描画とビジネスをクリーンに分ける(公開用) by Kenji Tanaka
描画とビジネスをクリーンに分ける(公開用)描画とビジネスをクリーンに分ける(公開用)
描画とビジネスをクリーンに分ける(公開用)
Kenji Tanaka1.3K views
Awesome State Management for React and Other Virtual-Dom Libraries by FITC
Awesome State Management for React and Other Virtual-Dom LibrariesAwesome State Management for React and Other Virtual-Dom Libraries
Awesome State Management for React and Other Virtual-Dom Libraries
FITC545 views

Similar to A multi submission importer for easyform

Laravel 8 export data as excel file with example by
Laravel 8 export data as excel file with exampleLaravel 8 export data as excel file with example
Laravel 8 export data as excel file with exampleKaty Slemon
193 views42 slides
Working With The Symfony Admin Generator by
Working With The Symfony Admin GeneratorWorking With The Symfony Admin Generator
Working With The Symfony Admin GeneratorJohn Cleveley
31.2K views66 slides
Gutenberg sous le capot, modules réutilisables by
Gutenberg sous le capot, modules réutilisablesGutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisablesRiad Benguella
1.6K views68 slides
Django crush course by
Django crush course Django crush course
Django crush course Mohammed El Rafie Tarabay
143 views55 slides
HTML::FormFu talk for Sydney PM by
HTML::FormFu talk for Sydney PMHTML::FormFu talk for Sydney PM
HTML::FormFu talk for Sydney PMDean Hamstead
1.2K views38 slides
Django quickstart by
Django quickstartDjango quickstart
Django quickstartMarconi Moreto
1.7K views37 slides

Similar to A multi submission importer for easyform(20)

Laravel 8 export data as excel file with example by Katy Slemon
Laravel 8 export data as excel file with exampleLaravel 8 export data as excel file with example
Laravel 8 export data as excel file with example
Katy Slemon193 views
Working With The Symfony Admin Generator by John Cleveley
Working With The Symfony Admin GeneratorWorking With The Symfony Admin Generator
Working With The Symfony Admin Generator
John Cleveley31.2K views
Gutenberg sous le capot, modules réutilisables by Riad Benguella
Gutenberg sous le capot, modules réutilisablesGutenberg sous le capot, modules réutilisables
Gutenberg sous le capot, modules réutilisables
Riad Benguella1.6K views
HTML::FormFu talk for Sydney PM by Dean Hamstead
HTML::FormFu talk for Sydney PMHTML::FormFu talk for Sydney PM
HTML::FormFu talk for Sydney PM
Dean Hamstead1.2K views
Hidden Docs in Angular by Yadong Xie
Hidden Docs in AngularHidden Docs in Angular
Hidden Docs in Angular
Yadong Xie787 views
Web2py Code Lab by Colin Su
Web2py Code LabWeb2py Code Lab
Web2py Code Lab
Colin Su5K views
Javascript Application Architecture with Backbone.JS by Min Ming Lo
Javascript Application Architecture with Backbone.JSJavascript Application Architecture with Backbone.JS
Javascript Application Architecture with Backbone.JS
Min Ming Lo2.8K views
Protractor framework – how to make stable e2e tests for Angular applications by Ludmila Nesvitiy
Protractor framework – how to make stable e2e tests for Angular applicationsProtractor framework – how to make stable e2e tests for Angular applications
Protractor framework – how to make stable e2e tests for Angular applications
Ludmila Nesvitiy4.4K views
Sahana Eden - Introduction to the Code by AidIQ
Sahana Eden - Introduction to the CodeSahana Eden - Introduction to the Code
Sahana Eden - Introduction to the Code
AidIQ516 views
Models, controllers and views by priestc
Models, controllers and viewsModels, controllers and views
Models, controllers and views
priestc291 views
Java Airline Reservation System – Travel Smarter, Not Harder.pdf by SudhanshiBakre1
Java Airline Reservation System – Travel Smarter, Not Harder.pdfJava Airline Reservation System – Travel Smarter, Not Harder.pdf
Java Airline Reservation System – Travel Smarter, Not Harder.pdf
SudhanshiBakre111 views
Nagios Conference 2013 - Jake Omann - Developing Nagios XI Components and Wiz... by Nagios
Nagios Conference 2013 - Jake Omann - Developing Nagios XI Components and Wiz...Nagios Conference 2013 - Jake Omann - Developing Nagios XI Components and Wiz...
Nagios Conference 2013 - Jake Omann - Developing Nagios XI Components and Wiz...
Nagios1.2K views
Mvc4 crud operations.-kemuning senja by alifha12
Mvc4 crud operations.-kemuning senjaMvc4 crud operations.-kemuning senja
Mvc4 crud operations.-kemuning senja
alifha12218 views

Recently uploaded

Elevating Privacy and Security in CloudStack - Boris Stoyanov - ShapeBlue by
Elevating Privacy and Security in CloudStack - Boris Stoyanov - ShapeBlueElevating Privacy and Security in CloudStack - Boris Stoyanov - ShapeBlue
Elevating Privacy and Security in CloudStack - Boris Stoyanov - ShapeBlueShapeBlue
222 views7 slides
Future of AR - Facebook Presentation by
Future of AR - Facebook PresentationFuture of AR - Facebook Presentation
Future of AR - Facebook PresentationRob McCarty
64 views27 slides
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ... by
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...ShapeBlue
184 views12 slides
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit... by
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...ShapeBlue
159 views25 slides
DRBD Deep Dive - Philipp Reisner - LINBIT by
DRBD Deep Dive - Philipp Reisner - LINBITDRBD Deep Dive - Philipp Reisner - LINBIT
DRBD Deep Dive - Philipp Reisner - LINBITShapeBlue
180 views21 slides
Webinar : Desperately Seeking Transformation - Part 2: Insights from leading... by
Webinar : Desperately Seeking Transformation - Part 2:  Insights from leading...Webinar : Desperately Seeking Transformation - Part 2:  Insights from leading...
Webinar : Desperately Seeking Transformation - Part 2: Insights from leading...The Digital Insurer
90 views52 slides

Recently uploaded(20)

Elevating Privacy and Security in CloudStack - Boris Stoyanov - ShapeBlue by ShapeBlue
Elevating Privacy and Security in CloudStack - Boris Stoyanov - ShapeBlueElevating Privacy and Security in CloudStack - Boris Stoyanov - ShapeBlue
Elevating Privacy and Security in CloudStack - Boris Stoyanov - ShapeBlue
ShapeBlue222 views
Future of AR - Facebook Presentation by Rob McCarty
Future of AR - Facebook PresentationFuture of AR - Facebook Presentation
Future of AR - Facebook Presentation
Rob McCarty64 views
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ... by ShapeBlue
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
Backup and Disaster Recovery with CloudStack and StorPool - Workshop - Venko ...
ShapeBlue184 views
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit... by ShapeBlue
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...
Transitioning from VMware vCloud to Apache CloudStack: A Path to Profitabilit...
ShapeBlue159 views
DRBD Deep Dive - Philipp Reisner - LINBIT by ShapeBlue
DRBD Deep Dive - Philipp Reisner - LINBITDRBD Deep Dive - Philipp Reisner - LINBIT
DRBD Deep Dive - Philipp Reisner - LINBIT
ShapeBlue180 views
Webinar : Desperately Seeking Transformation - Part 2: Insights from leading... by The Digital Insurer
Webinar : Desperately Seeking Transformation - Part 2:  Insights from leading...Webinar : Desperately Seeking Transformation - Part 2:  Insights from leading...
Webinar : Desperately Seeking Transformation - Part 2: Insights from leading...
Keynote Talk: Open Source is Not Dead - Charles Schulz - Vates by ShapeBlue
Keynote Talk: Open Source is Not Dead - Charles Schulz - VatesKeynote Talk: Open Source is Not Dead - Charles Schulz - Vates
Keynote Talk: Open Source is Not Dead - Charles Schulz - Vates
ShapeBlue252 views
How to Re-use Old Hardware with CloudStack. Saving Money and the Environment ... by ShapeBlue
How to Re-use Old Hardware with CloudStack. Saving Money and the Environment ...How to Re-use Old Hardware with CloudStack. Saving Money and the Environment ...
How to Re-use Old Hardware with CloudStack. Saving Money and the Environment ...
ShapeBlue166 views
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f... by TrustArc
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...
TrustArc170 views
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or... by ShapeBlue
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...
ShapeBlue198 views
The Role of Patterns in the Era of Large Language Models by Yunyao Li
The Role of Patterns in the Era of Large Language ModelsThe Role of Patterns in the Era of Large Language Models
The Role of Patterns in the Era of Large Language Models
Yunyao Li85 views
Confidence in CloudStack - Aron Wagner, Nathan Gleason - Americ by ShapeBlue
Confidence in CloudStack - Aron Wagner, Nathan Gleason - AmericConfidence in CloudStack - Aron Wagner, Nathan Gleason - Americ
Confidence in CloudStack - Aron Wagner, Nathan Gleason - Americ
ShapeBlue130 views
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue by ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlueMigrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
ShapeBlue218 views
Business Analyst Series 2023 - Week 4 Session 8 by DianaGray10
Business Analyst Series 2023 -  Week 4 Session 8Business Analyst Series 2023 -  Week 4 Session 8
Business Analyst Series 2023 - Week 4 Session 8
DianaGray10123 views
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ... by ShapeBlue
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...
ShapeBlue186 views
Declarative Kubernetes Cluster Deployment with Cloudstack and Cluster API - O... by ShapeBlue
Declarative Kubernetes Cluster Deployment with Cloudstack and Cluster API - O...Declarative Kubernetes Cluster Deployment with Cloudstack and Cluster API - O...
Declarative Kubernetes Cluster Deployment with Cloudstack and Cluster API - O...
ShapeBlue132 views
What’s New in CloudStack 4.19 - Abhishek Kumar - ShapeBlue by ShapeBlue
What’s New in CloudStack 4.19 - Abhishek Kumar - ShapeBlueWhat’s New in CloudStack 4.19 - Abhishek Kumar - ShapeBlue
What’s New in CloudStack 4.19 - Abhishek Kumar - ShapeBlue
ShapeBlue263 views
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T by ShapeBlue
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&TCloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
ShapeBlue152 views

A multi submission importer for easyform

  • 2. Annette Lewis Developer @ Six Feet Up, Inc. annette@sixfeetup.com 2
  • 3. The Setup ✖ Created several lengthy forms to register attendees for an event ✖ Needed a way to mass import attendee registrations ✖ Allow non Site Admins to prepare import data 3
  • 4. The Solution should: ✖ Work with most forms ✖ Provide a template to be filled out ✖ Allow Site Admins to preview data before submitting import ✖ Execute form actions 4
  • 5. Let's see this in action! The REsulting Solution 5
  • 6. What I know about EasyForm ✖ EasyForm uses Dexterity ✖ Dexterity uses the z3c.form library to build its forms, via the plone.z3cform integration package ✖ Dexterity also relies on plone.autoform, in particular its AutoExtensibleForm base class 6
  • 9. 9 <browser:page name="download-form-csv" for="collective.easyform.interfaces.IEasyForm" class="addon.policy.browser.views.GetCSVTemplateView" permission="cmf.ModifyPortalContent" layer="addon.policy.interfaces.IAddonPolicyLayer" /> <h2>Get csv template</h2> <p>Use this file as a template for submitting your information.</p> <form method="post" tal:attributes="action string:@@download-form-csv"> <input tal:replace="structure context/@@authenticator/authenticator" /> <input type="submit" value="Download CSV template" /> </form> <br/> Browser view and Download Button
  • 10. 10from collective.easyform.api import get_schema from collective.easyform.api import getFieldsInOrder from six import StringIO import csv from DateTime import DateTime class GetCSVTemplateView(BrowserView): def __call__(self, *args, **kwargs): # download csv template for all form fields form = self.context response = self.request.RESPONSE schema = get_schema(form) fields = getFieldsInOrder(schema) fieldnames = [] for f in fields: fieldnames.append(f[0]) now = DateTime().ISO().replace(" ", "-").replace(":", "") self.request.RESPONSE.setHeader( "Content-type", "text/comma-separated-values") self.request.RESPONSE.setHeader( "Content-Disposition", 'attachment; filename="{0}_{1}.csv"'.format(form.__name__, now), ) csv_file = StringIO() writer = csv.DictWriter(csv_file, fieldnames=fieldnames) writer.writeheader() return csv_file.getvalue()
  • 14. 14 <browser:page name="preview-csv-import" for="collective.easyform.interfaces.IEasyForm" class="addon.policy.browser.views.PreviewCsvDataView" permission="cmf.ModifyPortalContent" layer="addon.policy.interfaces.IAddonPolicyLayer" /> <h2>Import csv</h2> <p>Upload the csv template here.</p> <form action="@@preview-csv-import" method="post" enctype="multipart/form-data"> <input tal:replace="structure context/@@authenticator/authenticator" /> <input type=file name="file_attachment"><br> <input type=submit value="Import csv data"> </form> </div> Import Button and Browser View
  • 15. Reuse & REcycle Reuse as much existing code as possible and only add (override) what I need to 15
  • 16. 16 class CrudForm(AbstractCrudForm, form.Form): template = viewpagetemplatefile.ViewPageTemplateFile('crud-master.pt') description = u'' editform_factory = EditForm addform_factory = AddForm def update(self): super(CrudForm, self).update() addform = self.addform_factory(self, self.request) editform = self.editform_factory(self, self.request) addform.update() editform.update() self.subforms = [editform, addform] from plone.z3cform.crud import crud class SavedDataForm(crud.CrudForm): template = ViewPageTemplateFile("saveddata_form.pt") addform_factory = crud.NullForm Down the rabbit hole plone.z3cform/crud.py collective.easyform/browser/actions.py
  • 18. 18
  • 19. 19 from collective.easyform.browser.actions import SavedDataForm from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile class csvimportcrud(SavedDataForm): """ Use the SavedData Adapter form to review and validate csv import data """ template = ViewPageTemplateFile("templates/review_import.pt") editform_factory = ImporterEditForm from plone.z3cform import layout PreviewCsvDataView = layout.wrap_form(csvimportcrud) Building the Import Preview <div class="crud-form" tal:repeat="form view/subforms" tal:content="structure form/render" > </div>
  • 20. 20 def get_items(): """Subclasses must a list of all items to edit. This list contains tuples of the form ``(id, item)``, where the id is a unique identifiers to the items. The items must be adaptable to the schema returned by ``update_schema`` and ``view_schema`` methods. """ class SavedDataForm(crud.CrudForm): [...] def get_items(self): return [ (key, DataWrapper(key, value, self.context)) for key, value in self.field.getSavedFormInputItems() ] Formatting my import Data plone.z3cform/crud.py collective.easyform/browser/actions.py
  • 22. 22class csvimportcrud(SavedDataForm): [...] def importCSV(self, *args, **kwargs): """Import CSV information """ form = self.context request = self.request attachment = request.form['file_attachment'] data = self.getCSVdata(attachment, form) return data def getCSVdata(self, attachment, form): # get form fields schema = get_schema(form) fields = getFieldsInOrder(schema) fieldnames = [] for f in fields: fieldnames.append(f[0]) # open csv file if hasattr(attachment, 'file'): data = attachment.file.read() data = data.decode('UTF-8') file = data.splitlines() reader = csv.DictReader(file) rows = list(reader) # # format to match SavedDataForm expects formatted_rows = [] for row in rows: unsorted_data = dict(row) unsorted_data = self.cleanup_values(form, row) formatted_rows.append(unsorted_data) return [(count, dict(formatted_rows)) for count, formatted_rows in enumerate(formatted_rows, 1)]
  • 23. 23 def migrate_saved_data(ploneformgen, easyform): [...] for key, value in zip(cols, row): field = schema.get(key) value = value.decode('utf8') if IFromUnicode.providedBy(field): value = field.fromUnicode(value) elif IDatetime.providedBy(field) and value: value = DateTime(value).asdatetime() elif IDate.providedBy(field) and value: value = DateTime(value).asdatetime().date() elif ISet.providedBy(field): try: value = set(literal_eval(value)) except ValueError: pass elif INamedBlobFileField.providedBy(field): value = None data[key] = value Clean-up values Inspired by collective.easyform/migration/data.py - migrate_saved_data collective.easyform/api.py
  • 24. 24 def cleanup(value): """Accepts lists, tuples or comma/semicolon-separated strings and returns a list of native strings. """ if isinstance(value, six.string_types): value = safe_unicode(value).strip() value = value.replace(u",", u"n").replace(u";", u"n") value = [s for s in value.splitlines()] if isinstance(value, (list, tuple)): value = [safe_unicode(s).strip() for s in value] if six.PY2: # py2 expects a list of bytes value = [s.encode("utf-8") for s in value if s] return value Clean-up values pt 2 Also take advantage of easyform's cleanup() collective.easyform/api.py
  • 25. 25 def get_items(self): """ Get csv data and format """ data = [] if hasattr(self.request, 'file_attachment'): if self.request.form.get('file_attachment'): data = self.importCSV() return data elif hasattr(self.request, 'crud-edit.form.buttons.edit'): # get data from submitted form if self.request.form.get('data_export'): data_str = self.request.data_export # ast cannot handle sets data = ast.literal_eval(data_str) else: data = [] url = "{0}/@@import-forms".format( self.context.absolute_url() ) IStatusMessage(self.request).add( _(u'Error: Missing file attachment.')) self.request.response.redirect(url) return data The new get_items
  • 26. REview the Import 26 Pt 2 - Handling the Data
  • 27. 27 from collective.easyform.browser.actions import SavedDataForm from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile class csvimportcrud(SavedDataForm): """ Use the SavedData Adapter form to review and validate csv import data """ template = ViewPageTemplateFile("templates/review_import.pt") editform_factory = ImporterEditForm class ImporterEditForm(crud.EditForm): template = ViewPageTemplateFile('templates/importer-crud-table.pt') Displaying the Import Data <input name="data_export" type="hidden" value="" tal:attributes="value view/getData|nothing">
  • 30. 30 @button.buttonAndHandler( PMF(u"Submit"), name="submit", condition=lambda form: not form.thanksPage ) def handleSubmit(self, action): unsorted_data, errors = self.extractData() if errors: self.status = self.formErrorsMessage return unsorted_data = self.updateServerSideData(unsorted_data) errors = self.processActions(unsorted_data) if errors: return self.setErrorsMessage(errors) data = OrderedDict( [x for x in getFieldsInOrder(self.schema) if x[0] in unsorted_data] ) data.update(unsorted_data) [...] EasyFOrm handleSubmit()
  • 31. 31 class ImporterEditForm(crud.EditForm): [...] @button.buttonAndHandler(_('Import form Data'), name='edit', condition=lambda form: form.context.update_schema) def handle_edit(self, action): success = _(u"Successfully updated") partly_success = _(u"Some of your changes could not be applied.") status = no_changes = _(u"No changes made.") for subform in self.subforms: data, errors = subform.extractData() if errors: [...] imported_rows = 0 form = self.context.context rows = self.subforms total_rows = len(rows) for x in rows: row = data try: csvimportcrud(self.context, self.request).submitForm(form, row) imported_rows += 1 except IndexError: continue # self.status = status IStatusMessage(self.request).add( _(u'Imported {0} out of {1} row(s).').format(imported_rows, total_rows)) My entry point
  • 32. 32 from collective.easyform.browser.view import EasyFormForm as easyformview class csvimportcrud(SavedDataForm): """ Use the SavedData Adapter form to review and validate csv import data """ def submitForm(self, form, row): """ Process Easyform actions """ unsorted_data = row errors = easyformview.processActions(self.context, unsorted_data) if errors: return form.setErrorsMessage(errors) Submit the Subforms
  • 33. The beginning? A client request that taught me a lot and has a pretty cool result. 33
  • 34. Thanks For coming!! Any questions? You can find me at: annette@sixfeetup.com 34