Ignore:
Timestamp:
24 Nov 2014, 11:27:53 (10 years ago)
Author:
Henrik Bettermann
Message:

Add pdf utility methods first pdf slip components.

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  
    692692        return
    693693
     694class 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
    694709# Pages for customer documents
    695710
     
    915930            wf_info.fireTransition(transition_id)
    916931        return
     932
     933class 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  
    4444from waeup.ikoba.tests.test_async import FunctionalAsyncTestCase
    4545from waeup.ikoba.documents.workflow import VERIFIED
     46from waeup.ikoba.browser.tests.test_pdf import samples_dir
    4647
    4748PH_LEN = 15911  # Length of placeholder file
     
    504505            'temp_password generated: %s' % temp_password in logcontent)
    505506
     507
    506508class CustomerUITests(CustomersFullSetup):
    507509    # Tests for Customer class views and pages
     
    797799
    798800
    799 class CustomerDocumentUITests(CustomersFullSetup):
     801class DocumentUITests(CustomersFullSetup):
    800802    # Tests for CustomerDocument relates views and pages
    801803
     
    980982        self.assertTrue('Only the following extensions are allowed'
    981983            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  
    2121import os
    2222import grok
     23from reportlab.lib import colors
     24from reportlab.lib.units import cm
     25from reportlab.lib.pagesizes import A4
     26from reportlab.lib.styles import getSampleStyleSheet
     27from reportlab.platypus import Paragraph, Image, Table, Spacer
     28from zope.event import notify
     29from zope.schema.interfaces import ConstraintNotSatisfied
     30from zope.component import getUtility, createObject
     31from zope.formlib.form import setUpEditWidgets
     32from zope.i18n import translate
    2333from waeup.ikoba.interfaces import MessageFactory as _
    24 from waeup.ikoba.interfaces import CREATED, STARTED, REQUESTED, APPROVED
     34from waeup.ikoba.interfaces import (
     35    CREATED, STARTED, REQUESTED, APPROVED, IIkobaUtils, IExtFileStore)
    2536from waeup.ikoba.customers.interfaces import ICustomersUtils
     37from waeup.ikoba.browser.interfaces import IPDFCreator
     38from waeup.ikoba.browser.pdf import (
     39    ENTRY1_STYLE, format_html, NOTE_STYLE, HEADING_STYLE,
     40    get_signature_tables, get_qrcode)
    2641
    2742RE_CUSTID_NON_NUM = re.compile('[^\d]+')
     
    6984    return folder_name
    7085
     86SLIP_STYLE = [
     87    ('VALIGN',(0,0),(-1,-1),'TOP'),
     88    #('FONT', (0,0), (-1,-1), 'Helvetica', 11),
     89    ]
     90
     91CONTENT_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
     101FONT_SIZE = 10
     102FONT_COLOR = 'black'
     103
     104def trans(text, lang):
     105    # shortcut
     106    return translate(text, 'waeup.kofa', target_language=lang)
     107
     108
     109def 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
     137def 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
     198def 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
     221def 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
     260def 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
    71268class CustomersUtils(grok.GlobalUtility):
    72269    """A collection of methods subject to customization.
     
    90287        APPROVED: _('approved'),
    91288        }
     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.