|
@@ -0,0 +1,539 @@
|
|
1
|
+#!/usr/bin/env python
|
|
2
|
+#-*- coding:utf-8 -*-
|
|
3
|
+# vim: set ts=4 sts=4
|
|
4
|
+
|
|
5
|
+# For now the internal nametag templates will be hardcoded. At least a [default]
|
|
6
|
+# section is required; if a named section is missing the HTML will be ignored,
|
|
7
|
+# otherwise the following sections and files are allowed:
|
|
8
|
+# - [parent]
|
|
9
|
+# - [report]
|
|
10
|
+# - [volunteer]
|
|
11
|
+
|
|
12
|
+# TODO: respect locale's date/time format when doing substitution. Hard coded for now.
|
|
13
|
+# TODO: abstract barcode generation in themes. Hard coded for now.
|
|
14
|
+# TODO: a bunch of caching and restructuring.
|
|
15
|
+# TODO: code for multiple copies, if needed.
|
|
16
|
+# TODO: speed up batch printing with multi-page patched wkhtmltopdf?
|
|
17
|
+
|
|
18
|
+"""Handles generation of HTML for nametags, saving/reading printer config, etc"""
|
|
19
|
+
|
|
20
|
+import os
|
|
21
|
+import sys
|
|
22
|
+import re
|
|
23
|
+import platform
|
|
24
|
+import logging
|
|
25
|
+import subprocess
|
|
26
|
+import tempfile
|
|
27
|
+import datetime
|
|
28
|
+from configobj import ConfigObj
|
|
29
|
+
|
|
30
|
+#from codebar import codebar
|
|
31
|
+PRINT_MODE = 'pdf'
|
|
32
|
+
|
|
33
|
+# Platforms using the CUPS printing system (UNIX):
|
|
34
|
+unix = ['Linux', 'linux2', 'Darwin']
|
|
35
|
+wkhtmltopdf = '/usr/bin/wkhtmltopdf' #path to wkhtmltopdf binary
|
|
36
|
+#TODO: Option to select native or builtin wkhtmltopdf
|
|
37
|
+
|
|
38
|
+#TODO: Determine whether to use ~/.taxidi/resources/ (config.ini in ~/.taxidi/)
|
|
39
|
+# or $PWD/resources/ (config.ini in pwd).
|
|
40
|
+script_path = os.path.dirname(os.path.realpath(__file__))
|
|
41
|
+nametags = os.path.join(script_path, 'resources', 'nametag') #path where html can be found.
|
|
42
|
+#For distribution this may be moved to /usr/share/taxidi/ in UNIX, but should be
|
|
43
|
+#copied to user's ~/.taxidi/ as write access is needed.
|
|
44
|
+lpr = '/usr/bin/lp' #path to LPR program (CUPS/Unix only).
|
|
45
|
+
|
|
46
|
+class Printer:
|
|
47
|
+ def __init__(self, local=False):
|
|
48
|
+ self.log = logging.getLogger(__name__)
|
|
49
|
+ if local:
|
|
50
|
+ self.con = _DummyPrinter()
|
|
51
|
+ else:
|
|
52
|
+ if platform.system() in unix:
|
|
53
|
+ self.log.info("System is type UNIX. Using CUPS.")
|
|
54
|
+ self.con = _CUPS()
|
|
55
|
+ elif platform.system() == 'win32':
|
|
56
|
+ self.log.info("System is type WIN32. Using GDI.")
|
|
57
|
+ #TODO implement win32 printing code
|
|
58
|
+ else:
|
|
59
|
+ self.log.warning("Unsupported platform. Printing by preview only.")
|
|
60
|
+
|
|
61
|
+ def buildArguments(self, config, section='default'):
|
|
62
|
+ """Returns list of arguments to pass to wkhtmltopdf. Requires config
|
|
63
|
+ dictionary and section name to read (if None reads [default]).
|
|
64
|
+ Works all in lowercase. If section name does not exist, will use
|
|
65
|
+ values from [default] instead.
|
|
66
|
+ """
|
|
67
|
+
|
|
68
|
+ try: #attempt to read passed section. If invalid, use default instead.
|
|
69
|
+ self.log.debug('Attempting to read print configuration for section {0}...'
|
|
70
|
+ .format(section))
|
|
71
|
+ piece = config[section]
|
|
72
|
+ except KeyError:
|
|
73
|
+ self.log.warn('No section for {0}: using default instead.'.format(section))
|
|
74
|
+ piece = config['default']
|
|
75
|
+
|
|
76
|
+ args = []
|
|
77
|
+ if len(piece) == 0:
|
|
78
|
+ return [] #No options in section
|
|
79
|
+ for arg in piece.keys():
|
|
80
|
+ if arg.lower() == 'zoom':
|
|
81
|
+ args.append('--zoom')
|
|
82
|
+ args.append(piece[arg])
|
|
83
|
+ elif arg.lower() == 'size':
|
|
84
|
+ args.append('-s')
|
|
85
|
+ args.append(piece[arg])
|
|
86
|
+ elif arg.lower() == 'height':
|
|
87
|
+ args.append('--page-height')
|
|
88
|
+ args.append(piece[arg])
|
|
89
|
+ elif arg.lower() == 'width':
|
|
90
|
+ args.append('--page-width')
|
|
91
|
+ args.append(piece[arg])
|
|
92
|
+ elif arg.lower() == 'left':
|
|
93
|
+ args.append('--margin-left')
|
|
94
|
+ args.append(piece[arg])
|
|
95
|
+ elif arg.lower() == 'right':
|
|
96
|
+ args.append('--margin-right')
|
|
97
|
+ args.append(piece[arg])
|
|
98
|
+ elif arg.lower() == 'top':
|
|
99
|
+ args.append('--margin-top')
|
|
100
|
+ args.append(piece[arg])
|
|
101
|
+ elif arg.lower() == 'bottom':
|
|
102
|
+ args.append('--margin-bottom')
|
|
103
|
+ args.append(piece[arg])
|
|
104
|
+ elif arg.lower() == 'orientation':
|
|
105
|
+ args.append('--orientation')
|
|
106
|
+ args.append(piece[arg].lower())
|
|
107
|
+ else:
|
|
108
|
+ self.log.warning("Unexpected key encountered in {0}: {1} = {2}"
|
|
109
|
+ .format(section, arg, piece[arg]))
|
|
110
|
+ return args
|
|
111
|
+
|
|
112
|
+ def writePdf(self, args, html, copies=1, collate=True):
|
|
113
|
+ """
|
|
114
|
+ Calls wkhtmltopdf and generates a pdf. Accepts args as a list, path
|
|
115
|
+ to html file, and returns path to temporary file. Temp file
|
|
116
|
+ should be unlinked when no longer needed.
|
|
117
|
+ Also accepts copies=1, collate=True
|
|
118
|
+ """
|
|
119
|
+ #Build arguments
|
|
120
|
+ if copies < 0: copies = 1
|
|
121
|
+ if copies != 1:
|
|
122
|
+ args.append("--copies")
|
|
123
|
+ args.append(copies)
|
|
124
|
+ if collate:
|
|
125
|
+ args.append("--collate")
|
|
126
|
+
|
|
127
|
+ if type(html) == list:
|
|
128
|
+ args += html
|
|
129
|
+ else:
|
|
130
|
+ args.append(html)
|
|
131
|
+
|
|
132
|
+ #create temp file to write to
|
|
133
|
+ out = tempfile.NamedTemporaryFile(delete=False)
|
|
134
|
+ out.close()
|
|
135
|
+ args.append(out.name) #append output file name
|
|
136
|
+
|
|
137
|
+ self.log.debug('Calling {0} with arguments <'.format(wkhtmltopdf))
|
|
138
|
+ self.log.debug('{0} >'.format(args))
|
|
139
|
+
|
|
140
|
+ if args[0] != wkhtmltopdf: args.insert(0, wkhtmltopdf) #prepend program name
|
|
141
|
+
|
|
142
|
+ ret = subprocess.check_call(args)
|
|
143
|
+ if ret != 0:
|
|
144
|
+ self.log.error('Called process error:')
|
|
145
|
+ self.log.error('Program returned exit code {0}'.
|
|
146
|
+ format(ret))
|
|
147
|
+ return 0
|
|
148
|
+ self.log.debug('Generated pdf {0}'.format(out.name))
|
|
149
|
+ return out.name
|
|
150
|
+
|
|
151
|
+ def preview(self, fileName):
|
|
152
|
+ """Opens a file using the default application. (Print preview)"""
|
|
153
|
+ if os.name == 'posix':
|
|
154
|
+ if sys.platform == 'darwin': #OS X
|
|
155
|
+ self.log.debug('Attempting to preview file {0} via open'
|
|
156
|
+ .format(fileName))
|
|
157
|
+ ret = subprocess.call(['/usr/bin/open', fileName])
|
|
158
|
+ if ret != 0:
|
|
159
|
+ self.log.error('open returned non-zero exit code {0}'.format(ret))
|
|
160
|
+ return 1
|
|
161
|
+ return 0
|
|
162
|
+ elif sys.platform == 'linux2': #linux
|
|
163
|
+ try: #attempt to use evince-previewer instead
|
|
164
|
+ self.log.debug('Attempting to preview file {0} with evince.'
|
|
165
|
+ .format(fileName))
|
|
166
|
+ ret=subprocess.Popen(('/usr/bin/evince-previewer', fileName))
|
|
167
|
+ if ret != 0:
|
|
168
|
+ self.log.error('evince returned non-zero exit code {0}'
|
|
169
|
+ .format(ret))
|
|
170
|
+ return 1
|
|
171
|
+ return 0
|
|
172
|
+ except OSError:
|
|
173
|
+ self.log.debug('evince unavailable.')
|
|
174
|
+
|
|
175
|
+ self.log.debug('Attempting to preview file {0} via xdg-open'
|
|
176
|
+ .format(fileName))
|
|
177
|
+ ret = subprocess.call(('/usr/bin/xdg-open', fileName))
|
|
178
|
+ if ret != 0:
|
|
179
|
+ self.log.error('xdg-open returned non-zero exit code {0}'.format(ret))
|
|
180
|
+ return 1
|
|
181
|
+ return 0
|
|
182
|
+ else:
|
|
183
|
+ self.log.warning('Unable to determine default viewer for POSIX platform {0}'.format(sys.platform))
|
|
184
|
+ self.log.warning('Please file a bug and attach a copy of this log.')
|
|
185
|
+
|
|
186
|
+ if os.name == 'nt': #UNTESTED
|
|
187
|
+ self.log.debug('Attempting to preview file {0} via win32.startfile'
|
|
188
|
+ .format(fileName))
|
|
189
|
+ os.startfile(filepath)
|
|
190
|
+ return 0
|
|
191
|
+
|
|
192
|
+ def printout(self, filename, printer=None, orientation=None):
|
|
193
|
+ if os.name == 'posix' or os.name == 'mac': #use CUPS/lpr
|
|
194
|
+ self.con.printout(filename, printer, orientation)
|
|
195
|
+
|
|
196
|
+ #---- printing proxy methods -----
|
|
197
|
+
|
|
198
|
+ def listPrinters(self):
|
|
199
|
+ """Returns list of names of available system printers (proxy)."""
|
|
200
|
+ return self.con.listPrinters()
|
|
201
|
+
|
|
202
|
+ def getPrinters(self):
|
|
203
|
+ """
|
|
204
|
+ Returns dictionary of printer name, description, location, and URI (proxy)
|
|
205
|
+ """
|
|
206
|
+ return self.con.getPrinters()
|
|
207
|
+
|
|
208
|
+class Nametag:
|
|
209
|
+ def __init__(self, barcode=False):
|
|
210
|
+ """
|
|
211
|
+ Format nametags with data using available HTML templates. Will
|
|
212
|
+ fetch barcode encoding options from global config; otherwise accepts
|
|
213
|
+ barcode=False as the initialization argument.
|
|
214
|
+ """
|
|
215
|
+ #TODO: source config options for barcode, etc. from global config.
|
|
216
|
+ self.barcodeEnable = barcode
|
|
217
|
+ self.log = logging.getLogger(__name__)
|
|
218
|
+
|
|
219
|
+ #Setup and compile regex replacements:
|
|
220
|
+ self.date_re = re.compile(r'%DATE%', re.IGNORECASE) #date
|
|
221
|
+ self.time_re = re.compile(r'%TIME%', re.IGNORECASE) #time
|
|
222
|
+ self.room_re = re.compile(r'%ROOM%', re.IGNORECASE) #room
|
|
223
|
+ self.first_re = re.compile(r'%FIRST%', re.IGNORECASE) #name
|
|
224
|
+ self.name_re = re.compile(r'%NAME%', re.IGNORECASE) #name
|
|
225
|
+ self.last_re = re.compile(r'%LAST%', re.IGNORECASE) #surname
|
|
226
|
+ self.medical_re = re.compile(r'%MEDICAL%', re.IGNORECASE) #medical
|
|
227
|
+ self.code_re = re.compile(r'%CODE%', re.IGNORECASE) #paging code
|
|
228
|
+ self.secure_re = re.compile(r'%S%', re.IGNORECASE) #security code
|
|
229
|
+ self.title_re = re.compile(r'%TITLE%', re.IGNORECASE) #security code
|
|
230
|
+ self.number_re = re.compile(r'%NUMBER%', re.IGNORECASE) #security code
|
|
231
|
+ self.level_re = re.compile(r'%LEVEL%', re.IGNORECASE) #security code
|
|
232
|
+ self.age_re = re.compile(r'%AGE%', re.IGNORECASE) #age
|
|
233
|
+
|
|
234
|
+ self.medicalIcon_re = re.compile(
|
|
235
|
+ r"window\.onload\s=\shide\('medical'\)\;", re.IGNORECASE)
|
|
236
|
+ self.volunteerIcon_re = re.compile(
|
|
237
|
+ r"window\.onload\s=\shide\('volunteer'\)\;", re.IGNORECASE)
|
|
238
|
+
|
|
239
|
+ def listTemplates(self, directory=nametags):
|
|
240
|
+ """Returns a list of the installed nametag themes"""
|
|
241
|
+ #TODO: add more stringent validation against config and html.
|
|
242
|
+ directory = os.path.abspath(directory)
|
|
243
|
+ self.log.debug("Searching for html templates in {0}".format(directory))
|
|
244
|
+ try:
|
|
245
|
+ resource = os.listdir(directory)
|
|
246
|
+ except OSError as e:
|
|
247
|
+ logger.error('({0})'.format(e))
|
|
248
|
+ return []
|
|
249
|
+
|
|
250
|
+ themes = []
|
|
251
|
+ #Remove any files from themes[] that are not directories:
|
|
252
|
+ for i in resource:
|
|
253
|
+ if os.path.isdir(os.path.join(directory, i)):
|
|
254
|
+ themes.append(i)
|
|
255
|
+
|
|
256
|
+ valid = []
|
|
257
|
+ #Check that each folder has the corresponding .conf file:
|
|
258
|
+ for i in themes:
|
|
259
|
+ if os.path.isfile(self._getTemplateFile(i)):
|
|
260
|
+ valid.append(i)
|
|
261
|
+
|
|
262
|
+ valid.sort()
|
|
263
|
+ del resource, themes
|
|
264
|
+ self.log.debug("Found templates {0}".format(valid))
|
|
265
|
+ return valid
|
|
266
|
+
|
|
267
|
+ def _getTemplateFile(self, theme, directory=nametags):
|
|
268
|
+ """
|
|
269
|
+ Returns full path to .conf file for an installed template pack and
|
|
270
|
+ check it exists.
|
|
271
|
+ """
|
|
272
|
+ directory = os.path.abspath(directory)
|
|
273
|
+ path = os.path.join(directory, theme, '{0}.conf'.format(theme))
|
|
274
|
+ #TODO: more stringent checks (is there a matching HTML file?)
|
|
275
|
+ if os.path.isfile(path):
|
|
276
|
+ return path
|
|
277
|
+ else:
|
|
278
|
+ self.log.warning("{0} does not exist or is not file.".format(path))
|
|
279
|
+
|
|
280
|
+ def _getTemplatePath(self, theme, directory=nametags):
|
|
281
|
+ """Returns absolute path only of template pack"""
|
|
282
|
+ return os.path.join(directory, theme)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+ def readConfig(self, theme):
|
|
286
|
+ """
|
|
287
|
+ Reads the configuration for a specified template pack. Returns
|
|
288
|
+ dictionary.
|
|
289
|
+ """
|
|
290
|
+ inifile = self._getTemplateFile(theme)
|
|
291
|
+ self.log.info("Reading template configuration from '{0}'"
|
|
292
|
+ .format(inifile))
|
|
293
|
+ config = ConfigObj(inifile)
|
|
294
|
+ try: #check for default section
|
|
295
|
+ default = config['default']
|
|
296
|
+ except KeyError as e:
|
|
297
|
+ self.log.error(e)
|
|
298
|
+ self.log.error("{0} contains no [default] section and is invalid."
|
|
299
|
+ .format(inifile))
|
|
300
|
+ raise KeyError("{0} contains no [default] section and is invalid."
|
|
301
|
+ .format(inifile))
|
|
302
|
+ del default
|
|
303
|
+ return config
|
|
304
|
+
|
|
305
|
+ def nametag(self, template='apis', name='', number='', title='', level='', age='', barcode=False):
|
|
306
|
+ #def nametag(self, template='default', room='', first='', last='', medical='',
|
|
307
|
+ # code='', secure='', barcode=True):
|
|
308
|
+
|
|
309
|
+ #Check that theme specified is valid:
|
|
310
|
+ if template != 'default':
|
|
311
|
+ themes = self.listTemplates()
|
|
312
|
+ if template not in themes:
|
|
313
|
+ self.log.error("Bad theme specified. Using default instead.")
|
|
314
|
+ template = 'default'
|
|
315
|
+ #Read in the HTML from template.
|
|
316
|
+ try:
|
|
317
|
+ directory = self._getTemplatePath(template)
|
|
318
|
+ except KeyError:
|
|
319
|
+ self.log.error("Unable to process template '{0}'. Aborting."
|
|
320
|
+ .format(template))
|
|
321
|
+ return None
|
|
322
|
+
|
|
323
|
+ #read in the HTML
|
|
324
|
+ self.log.debug('Generating nametag with nametag()')
|
|
325
|
+ self.log.debug('Reading {0}...'.format(os.path.join(directory,
|
|
326
|
+ 'default.html')))
|
|
327
|
+ f = open(os.path.join(directory, 'default.html'))
|
|
328
|
+ html = f.read()
|
|
329
|
+ f.close()
|
|
330
|
+
|
|
331
|
+ if len(html) == 0: #template file is empty: use default instead.
|
|
332
|
+ self.log.warn('HTML template file {0} is blank.'.format(
|
|
333
|
+ os.path.join(directory, 'default.html')))
|
|
334
|
+
|
|
335
|
+ #generate barcode of secure code, code128:
|
|
336
|
+ if barcode:
|
|
337
|
+ try:
|
|
338
|
+ if secure == '':
|
|
339
|
+ codebar.gen('code128', os.path.join(directory,
|
|
340
|
+ 'default-secure.png'), code)
|
|
341
|
+ else:
|
|
342
|
+ codebar.gen('code128', os.path.join(directory,
|
|
343
|
+ 'default-secure.png'), secure)
|
|
344
|
+ except NotImplementedError as e:
|
|
345
|
+ self.log.error('Unable to generate barcode: {0}'.format(e))
|
|
346
|
+ html = html.replace(u'default-secure.png', u'white.gif')
|
|
347
|
+ else:
|
|
348
|
+ #replace barcode image with white.gif
|
|
349
|
+ html = html.decode('utf-8').replace(u'default-secure.png', u'white.gif')
|
|
350
|
+ self.log.debug("Disabled barcode.")
|
|
351
|
+
|
|
352
|
+ #get the current date/time
|
|
353
|
+ now = datetime.datetime.now()
|
|
354
|
+
|
|
355
|
+ #Perform substitutions:
|
|
356
|
+ #html = self.date_re.sub(now.strftime("%a %d %b %Y"), html)
|
|
357
|
+ html = self.date_re.sub(now.strftime("%a<br>%Y-%m-%d"), html)
|
|
358
|
+ html = self.time_re.sub(now.strftime("%H:%M:%S"), html)
|
|
359
|
+ #Fix for if database returns None instead of empty string:
|
|
360
|
+ html = self.name_re.sub(name.encode('utf-8'), html.encode('utf-8'))
|
|
361
|
+ html = self.level_re.sub(str(level), html)
|
|
362
|
+ html = self.title_re.sub(str(title), html)
|
|
363
|
+ html = self.number_re.sub(str(number), html)
|
|
364
|
+ html = self.age_re.sub(str(age), html)
|
|
365
|
+
|
|
366
|
+ return html
|
|
367
|
+
|
|
368
|
+class _DummyPrinter:
|
|
369
|
+ def __init__(self):
|
|
370
|
+ self.con = None
|
|
371
|
+ def listPrinters(self):
|
|
372
|
+ return []
|
|
373
|
+ def getPrinters(self):
|
|
374
|
+ return {}
|
|
375
|
+ def returnDefault(self):
|
|
376
|
+ return ''
|
|
377
|
+ def printout(self, filename, printer=None, orientation=None):
|
|
378
|
+ raise PrinterError('No printer system available')
|
|
379
|
+
|
|
380
|
+class _CUPS:
|
|
381
|
+ def __init__(self):
|
|
382
|
+ self.log = logging.getLogger(__name__)
|
|
383
|
+ self.log.info("Connecting to CUPS server on localhost...")
|
|
384
|
+ try:
|
|
385
|
+ import cups
|
|
386
|
+ except ImportError as e:
|
|
387
|
+ self.log.error("CUPS module not available. Is CUPS installed? {0}".format(e))
|
|
388
|
+ self.con = cups.Connection()
|
|
389
|
+
|
|
390
|
+ def listPrinters(self):
|
|
391
|
+ """
|
|
392
|
+ Returns a list of the names of available system printers.
|
|
393
|
+ """
|
|
394
|
+ return self.con.getPrinters().keys()
|
|
395
|
+
|
|
396
|
+ def getPrinters(self):
|
|
397
|
+ """
|
|
398
|
+ Returns dictionary of printer name, description, location, and URI.
|
|
399
|
+ """
|
|
400
|
+ a = dict()
|
|
401
|
+ printers = self.con.getPrinters()
|
|
402
|
+ for item in printers:
|
|
403
|
+ info = printers[item]['printer-info']
|
|
404
|
+ location = printers[item]['printer-location']
|
|
405
|
+ uri = printers[item]['device-uri']
|
|
406
|
+ a[item] = { 'info' : info, 'location' : location, 'uri' : uri}
|
|
407
|
+ return a
|
|
408
|
+
|
|
409
|
+ def getDefault(self):
|
|
410
|
+ """Determines the user's default system or personal printer."""
|
|
411
|
+ return self.con.getDests()[None, None].name
|
|
412
|
+
|
|
413
|
+ def printout(self, filename, printer=None, orientation=None):
|
|
414
|
+ if printer != None: #set printer; if none, then use default (leave argument blank)
|
|
415
|
+ if printer not in self.listPrinters():
|
|
416
|
+ raise PrinterError('Specified printer is not available on this system')
|
|
417
|
+ printArgs = ['-P', printer]
|
|
418
|
+ printArgs = []
|
|
419
|
+ else:
|
|
420
|
+ printArgs = []
|
|
421
|
+
|
|
422
|
+ if orientation: #set orientation option
|
|
423
|
+ if orientation not in ['landscape', 'portrait']:
|
|
424
|
+ raise PrinterError('Bad orientation specification: {0}'.format(orientation))
|
|
425
|
+ #printArgs.append('-o')
|
|
426
|
+ #printArgs.append(orientation)
|
|
427
|
+
|
|
428
|
+ try: #see if file exists
|
|
429
|
+ f = open(filename)
|
|
430
|
+ except IOError:
|
|
431
|
+ raise PrinterError('The specified file does not exist: {0}'.format(filename))
|
|
432
|
+ else:
|
|
433
|
+ f.close()
|
|
434
|
+ printArgs.append("-o")
|
|
435
|
+ printArgs.append("2.00x1.00\"")
|
|
436
|
+ printArgs.append(filename) #append file to print.
|
|
437
|
+
|
|
438
|
+ ret = subprocess.check_call([lpr,] + printArgs)
|
|
439
|
+ if ret != 0:
|
|
440
|
+ raise PrinterError('{0} exited non-zero ({1}). Error spooling.'.format(lpr, ret))
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+class PrinterError(Exception):
|
|
444
|
+ def __init__(self, value=''):
|
|
445
|
+ if value == '':
|
|
446
|
+ self.error = 'Generic spooling error.'
|
|
447
|
+ else:
|
|
448
|
+ self.error = value
|
|
449
|
+ def __str__(self):
|
|
450
|
+ return repr(self.error)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+class Main:
|
|
454
|
+ def __init__(self, local=False):
|
|
455
|
+ self.log = logging.getLogger(__name__)
|
|
456
|
+ self.con = Printer(False)
|
|
457
|
+ self.tag = Nametag(True)
|
|
458
|
+ self.section = ''
|
|
459
|
+
|
|
460
|
+ def nametag(self, theme='apis', name='', number='', title='', level='', barcode=False, section='default'):
|
|
461
|
+ #def nametag(self, theme='default', room='', first='', last='', medical='',
|
|
462
|
+ # code='', secure='', barcode=True, section='default'):
|
|
463
|
+ """Note: section= not fully implemented in Nametag.nametag method"""
|
|
464
|
+ self.section = section
|
|
465
|
+ self.conf = self.tag.readConfig(theme) #theme
|
|
466
|
+ self.args = self.con.buildArguments(self.conf, section) #section
|
|
467
|
+
|
|
468
|
+ stuff = self.tag.nametag(name=name, number=number, title=title, template=theme, level=level)
|
|
469
|
+ temp_path = self.tag._getTemplatePath(theme)
|
|
470
|
+ html = tempfile.NamedTemporaryFile(delete=False, dir=temp_path, suffix='.html')
|
|
471
|
+ html.write(stuff.encode('utf-8'))
|
|
472
|
+ html.close()
|
|
473
|
+
|
|
474
|
+ self.pdf = self.con.writePdf(self.args, html.name)
|
|
475
|
+ os.unlink(html.name)
|
|
476
|
+ return self.pdf
|
|
477
|
+
|
|
478
|
+ def nametags(self, tags, theme='apis', section='default'):
|
|
479
|
+ self.section = section
|
|
480
|
+ self.conf = self.tag.readConfig(theme) #theme
|
|
481
|
+ self.args = self.con.buildArguments(self.conf, section) #section
|
|
482
|
+
|
|
483
|
+ html_files = []
|
|
484
|
+ for data in tags:
|
|
485
|
+ stuff = self.tag.nametag(
|
|
486
|
+ name=data['name'], number=data['number'],
|
|
487
|
+ title=data['title'], template=theme, level=data['level'],
|
|
488
|
+ age=data['age']
|
|
489
|
+ )
|
|
490
|
+ temp_path = self.tag._getTemplatePath(theme)
|
|
491
|
+ html = tempfile.NamedTemporaryFile(delete=False, dir=temp_path, suffix='.html')
|
|
492
|
+ html.write(stuff)
|
|
493
|
+ html.close()
|
|
494
|
+ html_files.append(html.name)
|
|
495
|
+
|
|
496
|
+ self.pdf = self.con.writePdf(self.args, html_files)
|
|
497
|
+ for tmpname in html_files:
|
|
498
|
+ #os.unlink(tmpname)
|
|
499
|
+ pass
|
|
500
|
+ return self.pdf
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+ def preview(self, filename=None):
|
|
504
|
+ if filename == None:
|
|
505
|
+ filename = self.pdf
|
|
506
|
+ self.con.preview(filename)
|
|
507
|
+
|
|
508
|
+ def printout(self, filename=None, printer=None, orientation=None):
|
|
509
|
+ if filename == None:
|
|
510
|
+ filename = self.pdf
|
|
511
|
+ if orientation == None:
|
|
512
|
+ orientation = self.conf[self.section]['orientation']
|
|
513
|
+ self.con.printout(filename, printer, orientation)
|
|
514
|
+
|
|
515
|
+ def cleanup(self, trash=None, *dt):
|
|
516
|
+ if trash != None:
|
|
517
|
+ for item in trash:
|
|
518
|
+ os.unlink(item)
|
|
519
|
+ else:
|
|
520
|
+ os.unlink(self.pdf)
|
|
521
|
+ self.section = ''
|
|
522
|
+
|
|
523
|
+if __name__ == '__main__':
|
|
524
|
+
|
|
525
|
+ tags = [
|
|
526
|
+ { 'name' : "Barkley Woofington", 'number' : "S-6969", 'level' : "Top Dog", 'title' : '' , 'age' : 20},
|
|
527
|
+ { 'name' : "Rechner Foxer", 'number' : "S-0001", 'level' : "Foxo", 'title' : '' , 'age' : 12}
|
|
528
|
+ ]
|
|
529
|
+
|
|
530
|
+ con = Main(False)
|
|
531
|
+ con.nametags(tags, theme='apis')
|
|
532
|
+ #con.nametag(theme='apis', name="Some Kind Of Horse", number="S-0000", title="Staff", level="Player")
|
|
533
|
+
|
|
534
|
+ print(con.pdf)
|
|
535
|
+ con.preview()
|
|
536
|
+ #con.printout(printer="LabelWriter-450-Turbo")
|
|
537
|
+
|
|
538
|
+ raw_input(">")
|
|
539
|
+ con.cleanup()
|