Changeset 12051 for main/waeup.ikoba/trunk
- Timestamp:
- 24 Nov 2014, 11:27:53 (10 years ago)
- Location:
- main/waeup.ikoba/trunk/src/waeup/ikoba/customers
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.ikoba/trunk/src/waeup/ikoba/customers/browser.py
r12039 r12051 692 692 return 693 693 694 class CustomerBasePDFFormPage(IkobaDisplayFormPage): 695 """ Page to display customer base data in pdf files. 696 """ 697 698 def __init__(self, context, request, omit_fields=()): 699 self.omit_fields = omit_fields 700 super(CustomerBasePDFFormPage, self).__init__(context, request) 701 702 @property 703 def form_fields(self): 704 form_fields = grok.AutoFields(ICustomer) 705 for field in self.omit_fields: 706 form_fields = form_fields.omit(field) 707 return form_fields 708 694 709 # Pages for customer documents 695 710 … … 915 930 wf_info.fireTransition(transition_id) 916 931 return 932 933 class ExportPDFDocumentsOverviewPage(UtilityView, grok.View): 934 """Deliver an overview slip. 935 """ 936 grok.context(ICustomer) 937 grok.name('overview_slip.pdf') 938 grok.require('waeup.viewCustomer') 939 prefix = 'form' 940 941 omit_fields = ('suspended', 'sex', 'suspended_comment') 942 943 form_fields = None 944 945 @property 946 def label(self): 947 portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE 948 return translate(_('Documents of'), 949 'waeup.ikoba', target_language=portal_language) \ 950 + ' %s' % self.context.display_fullname 951 952 def render(self): 953 customerview = CustomerBasePDFFormPage(self.context, 954 self.request, self.omit_fields) 955 customers_utils = getUtility(ICustomersUtils) 956 return customers_utils.renderPDF( 957 self, 'overview_slip.pdf', 958 self.context, customerview, 959 omit_fields=self.omit_fields) -
main/waeup.ikoba/trunk/src/waeup/ikoba/customers/tests/test_browser.py
r12040 r12051 44 44 from waeup.ikoba.tests.test_async import FunctionalAsyncTestCase 45 45 from waeup.ikoba.documents.workflow import VERIFIED 46 from waeup.ikoba.browser.tests.test_pdf import samples_dir 46 47 47 48 PH_LEN = 15911 # Length of placeholder file … … 504 505 'temp_password generated: %s' % temp_password in logcontent) 505 506 507 506 508 class CustomerUITests(CustomersFullSetup): 507 509 # Tests for Customer class views and pages … … 797 799 798 800 799 class CustomerDocumentUITests(CustomersFullSetup):801 class DocumentUITests(CustomersFullSetup): 800 802 # Tests for CustomerDocument relates views and pages 801 803 … … 980 982 self.assertTrue('Only the following extensions are allowed' 981 983 in self.browser.contents) 984 985 986 def test_view_slips(self): 987 self.browser.addHeader('Authorization', 'Basic mgr:mgrpw') 988 # Officers can open the pdf transcript 989 self.browser.open(self.customer_path + '/overview_slip.pdf') 990 self.assertEqual(self.browser.headers['Status'], '200 Ok') 991 self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf') 992 path = os.path.join(samples_dir(), 'overview_slip.pdf') 993 open(path, 'wb').write(self.browser.contents) 994 print "Sample PDF overview_slip.pdf written to %s" % path -
main/waeup.ikoba/trunk/src/waeup/ikoba/customers/utils.py
r12035 r12051 21 21 import os 22 22 import grok 23 from reportlab.lib import colors 24 from reportlab.lib.units import cm 25 from reportlab.lib.pagesizes import A4 26 from reportlab.lib.styles import getSampleStyleSheet 27 from reportlab.platypus import Paragraph, Image, Table, Spacer 28 from zope.event import notify 29 from zope.schema.interfaces import ConstraintNotSatisfied 30 from zope.component import getUtility, createObject 31 from zope.formlib.form import setUpEditWidgets 32 from zope.i18n import translate 23 33 from waeup.ikoba.interfaces import MessageFactory as _ 24 from waeup.ikoba.interfaces import CREATED, STARTED, REQUESTED, APPROVED 34 from waeup.ikoba.interfaces import ( 35 CREATED, STARTED, REQUESTED, APPROVED, IIkobaUtils, IExtFileStore) 25 36 from waeup.ikoba.customers.interfaces import ICustomersUtils 37 from waeup.ikoba.browser.interfaces import IPDFCreator 38 from waeup.ikoba.browser.pdf import ( 39 ENTRY1_STYLE, format_html, NOTE_STYLE, HEADING_STYLE, 40 get_signature_tables, get_qrcode) 26 41 27 42 RE_CUSTID_NON_NUM = re.compile('[^\d]+') … … 69 84 return folder_name 70 85 86 SLIP_STYLE = [ 87 ('VALIGN',(0,0),(-1,-1),'TOP'), 88 #('FONT', (0,0), (-1,-1), 'Helvetica', 11), 89 ] 90 91 CONTENT_STYLE = [ 92 ('VALIGN',(0,0),(-1,-1),'TOP'), 93 #('FONT', (0,0), (-1,-1), 'Helvetica', 8), 94 #('TEXTCOLOR',(0,0),(-1,0),colors.white), 95 #('BACKGROUND',(0,0),(-1,0),colors.black), 96 ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), 97 ('BOX', (0,0), (-1,-1), 1, colors.black), 98 99 ] 100 101 FONT_SIZE = 10 102 FONT_COLOR = 'black' 103 104 def trans(text, lang): 105 # shortcut 106 return translate(text, 'waeup.kofa', target_language=lang) 107 108 109 def formatted_text(text, color=FONT_COLOR, lang='en'): 110 """Turn `text`, `color` and `size` into an HTML snippet. 111 112 The snippet is suitable for use with reportlab and generating PDFs. 113 Wraps the `text` into a ``<font>`` tag with passed attributes. 114 115 Also non-strings are converted. Raw strings are expected to be 116 utf-8 encoded (usually the case for widgets etc.). 117 118 Finally, a br tag is added if widgets contain div tags 119 which are not supported by reportlab. 120 121 The returned snippet is unicode type. 122 """ 123 if not isinstance(text, unicode): 124 if isinstance(text, basestring): 125 text = text.decode('utf-8') 126 else: 127 text = unicode(text) 128 if text == 'None': 129 text = '' 130 # Mainly for boolean values we need our customized 131 # localisation of the zope domain 132 text = translate(text, 'zope', target_language=lang) 133 text = text.replace('</div>', '<br /></div>') 134 tag1 = u'<font color="%s">' % (color) 135 return tag1 + u'%s</font>' % text 136 137 def render_customer_data(customerview, context, omit_fields=(), 138 lang='en', slipname=None): 139 """Render customer table for an existing frame. 140 """ 141 width, height = A4 142 set_up_widgets(customerview, ignore_request=True) 143 data_left = [] 144 data_middle = [] 145 style = getSampleStyleSheet() 146 img = getUtility(IExtFileStore).getFileByContext( 147 customerview.context, attr='passport.jpg') 148 if img is None: 149 from waeup.ikoba.browser import DEFAULT_PASSPORT_IMAGE_PATH 150 img = open(DEFAULT_PASSPORT_IMAGE_PATH, 'rb') 151 doc_img = Image(img.name, width=4*cm, height=4*cm, kind='bound') 152 data_left.append([doc_img]) 153 #data.append([Spacer(1, 12)]) 154 155 f_label = trans(_('Name:'), lang) 156 f_label = Paragraph(f_label, ENTRY1_STYLE) 157 f_text = formatted_text(customerview.context.display_fullname) 158 f_text = Paragraph(f_text, ENTRY1_STYLE) 159 data_middle.append([f_label,f_text]) 160 161 for widget in customerview.widgets: 162 if 'name' in widget.name: 163 continue 164 f_label = translate( 165 widget.label.strip(), 'waeup.ikoba', 166 target_language=lang) 167 f_label = Paragraph('%s:' % f_label, ENTRY1_STYLE) 168 f_text = formatted_text(widget(), lang=lang) 169 f_text = Paragraph(f_text, ENTRY1_STYLE) 170 data_middle.append([f_label,f_text]) 171 172 if getattr(customerview.context, 'certcode', None): 173 if not 'date_of_birth' in omit_fields: 174 f_label = trans(_('Date of Birth:'), lang) 175 f_label = Paragraph(f_label, ENTRY1_STYLE) 176 date_of_birth = customerview.context.date_of_birth 177 tz = getUtility(IIkobaUtils).tzinfo 178 date_of_birth = to_timezone(date_of_birth, tz) 179 if date_of_birth is not None: 180 date_of_birth = date_of_birth.strftime("%d/%m/%Y") 181 f_text = formatted_text(date_of_birth) 182 f_text = Paragraph(f_text, ENTRY1_STYLE) 183 data_middle.append([f_label,f_text]) 184 185 # append QR code to the right 186 if slipname: 187 url = customerview.url(context, slipname) 188 data_right = [[get_qrcode(url, width=70.0)]] 189 table_right = Table(data_right,style=SLIP_STYLE) 190 else: 191 table_right = None 192 193 table_left = Table(data_left,style=SLIP_STYLE) 194 table_middle = Table(data_middle,style=SLIP_STYLE, colWidths=[5*cm, 5*cm]) 195 table = Table([[table_left, table_middle, table_right],],style=SLIP_STYLE) 196 return table 197 198 def render_table_data(tableheader, tabledata, lang='en'): 199 """Render children table for an existing frame. 200 """ 201 data = [] 202 #data.append([Spacer(1, 12)]) 203 line = [] 204 style = getSampleStyleSheet() 205 for element in tableheader: 206 field = '<strong>%s</strong>' % formatted_text(element[0], lang=lang) 207 field = Paragraph(field, style["Normal"]) 208 line.append(field) 209 data.append(line) 210 for ticket in tabledata: 211 line = [] 212 for element in tableheader: 213 field = formatted_text(getattr(ticket,element[1],u' ')) 214 field = Paragraph(field, style["Normal"]) 215 line.append(field) 216 data.append(line) 217 table = Table(data,colWidths=[ 218 element[2]*cm for element in tableheader], style=CONTENT_STYLE) 219 return table 220 221 def docs_as_flowables(view, lang='en'): 222 """Create reportlab flowables out of scanned docs. 223 """ 224 # XXX: fix circular import problem 225 from waeup.ikoba.customers.fileviewlets import FileManager 226 from waeup.ikoba.browser import DEFAULT_IMAGE_PATH 227 style = getSampleStyleSheet() 228 data = [] 229 230 # Collect viewlets 231 fm = FileManager(view.context, view.request, view) 232 fm.update() 233 if fm.viewlets: 234 sc_translation = trans(_('Scanned Documents'), lang) 235 data.append(Paragraph(sc_translation, HEADING_STYLE)) 236 # Insert list of scanned documents 237 table_data = [] 238 for viewlet in fm.viewlets: 239 if viewlet.file_exists: 240 # Show viewlet only if file exists 241 f_label = Paragraph(trans(viewlet.label, lang), ENTRY1_STYLE) 242 img_path = getattr(getUtility(IExtFileStore).getFileByContext( 243 view.context, attr=viewlet.download_name), 'name', None) 244 #f_text = Paragraph(trans(_('(not provided)'),lang), ENTRY1_STYLE) 245 if img_path is None: 246 pass 247 elif not img_path[-4:] in ('.jpg', '.JPG'): 248 # reportlab requires jpg images, I think. 249 f_text = Paragraph('%s (not displayable)' % ( 250 viewlet.title,), ENTRY1_STYLE) 251 else: 252 f_text = Image(img_path, width=2*cm, height=1*cm, kind='bound') 253 table_data.append([f_label, f_text]) 254 if table_data: 255 # safety belt; empty tables lead to problems. 256 data.append(Table(table_data, style=SLIP_STYLE)) 257 return data 258 259 260 def set_up_widgets(view, ignore_request=False): 261 view.adapters = {} 262 view.widgets = setUpEditWidgets( 263 view.form_fields, view.prefix, view.context, view.request, 264 adapters=view.adapters, for_display=True, 265 ignore_request=ignore_request 266 ) 267 71 268 class CustomersUtils(grok.GlobalUtility): 72 269 """A collection of methods subject to customization. … … 90 287 APPROVED: _('approved'), 91 288 } 289 290 def getPDFCreator(self, context=None): 291 """Get a pdf creator suitable for `context`. 292 293 The default implementation always returns the default creator. 294 """ 295 return getUtility(IPDFCreator) 296 297 def renderPDF(self, view, filename='slip.pdf', customer=None, 298 customerview=None, 299 tableheader=[], tabledata=[], 300 note=None, signatures=None, sigs_in_footer=(), 301 show_scans=True, show_history=True, topMargin=1.5, 302 omit_fields=()): 303 """Render pdf slips for various pages. 304 """ 305 portal_language = getUtility(IIkobaUtils).PORTAL_LANGUAGE 306 style = getSampleStyleSheet() 307 creator = self.getPDFCreator() 308 data = [] 309 doc_title = view.label 310 author = '%s (%s)' % (view.request.principal.title, 311 view.request.principal.id) 312 footer_text = view.label.split('\n') 313 if len(footer_text) > 2: 314 # We can add a department in first line 315 footer_text = footer_text[1] 316 else: 317 # Only the first line is used for the footer 318 footer_text = footer_text[0] 319 if getattr(customer, 'customer_id', None) is not None: 320 footer_text = "%s - %s - " % (customer.customer_id, footer_text) 321 322 # Insert customer data table 323 if customer is not None: 324 bd_translation = trans(_('Base Data'), portal_language) 325 data.append(Paragraph(bd_translation, HEADING_STYLE)) 326 data.append(render_customer_data( 327 customerview, view.context, omit_fields, lang=portal_language, 328 slipname=filename)) 329 330 # Insert widgets 331 if view.form_fields: 332 data.append(Paragraph(view.title, HEADING_STYLE)) 333 separators = getattr(self, 'SEPARATORS_DICT', {}) 334 table = creator.getWidgetsTable( 335 view.form_fields, view.context, None, lang=portal_language, 336 separators=separators) 337 data.append(table) 338 339 # Insert scanned docs 340 if show_scans: 341 data.extend(docs_as_flowables(view, portal_language)) 342 343 # Insert history 344 if show_history: 345 hist_translation = trans(_('Workflow History'), portal_language) 346 data.append(Paragraph(hist_translation, HEADING_STYLE)) 347 data.extend(creator.fromStringList(customer.history.messages)) 348 349 # Insert content tables (optionally on second page) 350 if hasattr(view, 'tabletitle'): 351 for i in range(len(view.tabletitle)): 352 if tabledata[i] and tableheader[i]: 353 #data.append(PageBreak()) 354 #data.append(Spacer(1, 20)) 355 data.append(Paragraph(view.tabletitle[i], HEADING_STYLE)) 356 data.append(Spacer(1, 8)) 357 contenttable = render_table_data(tableheader[i],tabledata[i]) 358 data.append(contenttable) 359 360 # Insert signatures 361 # XXX: We are using only sigs_in_footer in waeup.ikoba, so we 362 # do not have a test for the following lines. 363 if signatures and not sigs_in_footer: 364 data.append(Spacer(1, 20)) 365 # Render one signature table per signature to 366 # get date and signature in line. 367 for signature in signatures: 368 signaturetables = get_signature_tables(signature) 369 data.append(signaturetables[0]) 370 371 view.response.setHeader( 372 'Content-Type', 'application/pdf') 373 try: 374 pdf_stream = creator.create_pdf( 375 data, None, doc_title, author=author, footer=footer_text, 376 note=note, sigs_in_footer=sigs_in_footer, topMargin=topMargin) 377 except IOError: 378 view.flash('Error in image file.') 379 return view.redirect(view.url(view.context)) 380 return pdf_stream
Note: See TracChangeset for help on using the changeset viewer.