Merge with trunk-server
bzr revid: ysa@tinyerp.com-20110120053733-ws7mfcfw15p0iyy1
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
# -*- makefile -*-
|
||||
|
||||
addons-path := bin/addons/
|
||||
extra-addons:= ''
|
||||
root-path := bin/
|
||||
port := 8069
|
||||
net_port := 8070
|
||||
module := base
|
||||
database := terp
|
||||
language := fr_FR
|
||||
i18n-import := bin/addons/base/i18n/fr_FR.po
|
||||
interrogation_file := bin/addons/base_quality_interrogation.py
|
||||
login := admin
|
||||
password := admin
|
||||
|
||||
start:
|
||||
python $(interrogation_file) start-server --root-path=$(root-path) --addons-path=$(addons-path) --port=$(port) --net_port=$(net_port)
|
||||
|
||||
create-db:
|
||||
python $(interrogation_file) create-db --database=$(database) --root-path=$(root-path) --addons-path=$(addons-path) --port=$(port) --net_port=$(net_port) --login=$(login) --password=$(password)
|
||||
|
||||
drop-db:
|
||||
python $(interrogation_file) drop-db --database=$(database) --root-path=$(root-path) --addons-path=$(addons-path) --port=$(port) --net_port=$(net_port)
|
||||
|
||||
install-module:
|
||||
python $(interrogation_file) install-module --modules=$(module) --database=$(database) --root-path=$(root-path) --addons-path=$(addons-path) --extra-addons=$(extra-addons) --net_port=$(net_port) --port=$(port) --login=$(login) --password=$(password)
|
||||
|
||||
upgrade-module:
|
||||
python $(interrogation_file) upgrade-module --modules=$(module) --database=$(database) --root-path=$(root-path) --addons-path=$(addons-path) --port=$(port) --net_port=$(net_port) --login=$(login) --password=$(password)
|
||||
|
||||
|
||||
install-translation:
|
||||
python $(interrogation_file) install-translation --database=$(database) --translate-in=$(i18n-import) --net_port=$(net_port) --port=$(port) --login=$(login) --password=$(password) --root-path=$(root-path) --addons-path=$(addons-path)
|
||||
|
||||
|
||||
version:
|
||||
python bin/openerp-server.py --version
|
||||
|
||||
check-quality:
|
||||
python $(interrogation_file) check-quality --database=$(database) --modules=$(module) --net_port=$(net_port) --port=$(port) --login=$(login) --password=$(password) --addons-path=$(addons-path) --root-path=$(root-path)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
|
||||
# Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -286,8 +286,6 @@ def get_module_resource(module, *args):
|
||||
return resource_path
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def get_modules():
|
||||
"""Returns the list of module names
|
||||
"""
|
||||
@@ -679,8 +677,8 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
|
||||
cr.rollback()
|
||||
|
||||
def _load_data(cr, module_name, id_map, mode, kind):
|
||||
noupdate = (kind == 'demo')
|
||||
for filename in package.data.get(kind, []):
|
||||
noupdate = (kind == 'demo')
|
||||
_, ext = os.path.splitext(filename)
|
||||
log.info("module %s: loading %s", module_name, filename)
|
||||
pathname = os.path.join(module_name, filename)
|
||||
@@ -824,8 +822,8 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
|
||||
report = tools.assertion_report()
|
||||
# NOTE: Try to also load the modules that have been marked as uninstallable previously...
|
||||
STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
|
||||
if 'base' in tools.config['update']:
|
||||
cr.execute("update ir_module_module set state=%s where name=%s", ('to upgrade', 'base'))
|
||||
if 'base' in tools.config['update'] or 'all' in tools.config['update']:
|
||||
cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
|
||||
|
||||
# STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
|
||||
graph = create_graph(cr, ['base'], force)
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
'ir/wizard/wizard_menu_view.xml',
|
||||
'ir/ir.xml',
|
||||
'ir/workflow/workflow_view.xml',
|
||||
'ir/report/ir_report.xml',
|
||||
'module/module_view.xml',
|
||||
'module/module_data.xml',
|
||||
'module/module_report.xml',
|
||||
@@ -52,6 +53,7 @@
|
||||
'module/wizard/base_import_language_view.xml',
|
||||
'module/wizard/base_module_upgrade_view.xml',
|
||||
'module/wizard/base_module_configuration_view.xml',
|
||||
'module/wizard/base_module_shortcut_data.xml',
|
||||
'module/wizard/base_export_language_view.xml',
|
||||
'module/wizard/base_update_translations_view.xml',
|
||||
'res/res_request_view.xml',
|
||||
@@ -59,6 +61,7 @@
|
||||
'res/res_log_view.xml',
|
||||
'res/partner/partner_report.xml',
|
||||
'res/partner/partner_view.xml',
|
||||
'res/partner/partner_shortcut_data.xml',
|
||||
'res/bank_view.xml',
|
||||
'res/country_view.xml',
|
||||
'res/res_currency_view.xml',
|
||||
|
||||
@@ -1179,7 +1179,7 @@
|
||||
</record>
|
||||
|
||||
<record id="DKK" model="res.currency">
|
||||
<field name="name">kr</field>
|
||||
<field name="name">DKK</field>
|
||||
<field name="symbol">kr</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="accuracy">4</field>
|
||||
@@ -1238,7 +1238,7 @@
|
||||
|
||||
|
||||
<record id="NOK" model="res.currency">
|
||||
<field name="name">kr</field>
|
||||
<field name="name">NOK</field>
|
||||
<field name="symbol">kr</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="accuracy">4</field>
|
||||
@@ -1290,7 +1290,7 @@
|
||||
</record>
|
||||
|
||||
<record id="SEK" model="res.currency">
|
||||
<field name="name">kr</field>
|
||||
<field name="name">SEK</field>
|
||||
<field name="symbol">kr</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="accuracy">4</field>
|
||||
@@ -1304,7 +1304,7 @@
|
||||
|
||||
<record id="GBP" model="res.currency">
|
||||
<field name="name">GBP</field>
|
||||
<field name="symbol">₤</field>
|
||||
<field name="symbol">£</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="accuracy">4</field>
|
||||
<field name="company_id" ref="main_company"/>
|
||||
@@ -1419,19 +1419,6 @@
|
||||
<field eval="time.strftime('%Y-01-01')" name="name"/>
|
||||
</record>
|
||||
|
||||
<record id="EEK" model="res.currency">
|
||||
<field name="name">EEK</field>
|
||||
<field name="symbol">kr</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="accuracy">4</field>
|
||||
<field name="company_id" ref="main_company"/>
|
||||
</record>
|
||||
<record id="rateEEK" model="res.currency.rate">
|
||||
<field name="rate">15.6466</field>
|
||||
<field name="currency_id" ref="EEK"/>
|
||||
<field eval="time.strftime('%Y-01-01')" name="name"/>
|
||||
</record>
|
||||
|
||||
<record id="LTL" model="res.currency">
|
||||
<field name="name">LTL</field>
|
||||
<field name="symbol">Lt</field>
|
||||
@@ -1447,7 +1434,7 @@
|
||||
|
||||
<record id="RON" model="res.currency">
|
||||
<field name="name">RON</field>
|
||||
<field name="symbol">kr</field>
|
||||
<field name="symbol">lei</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="accuracy">4</field>
|
||||
<field name="company_id" ref="main_company"/>
|
||||
|
||||
+304
-184
File diff suppressed because it is too large
Load Diff
+304
-184
File diff suppressed because it is too large
Load Diff
+304
-184
File diff suppressed because it is too large
Load Diff
+270
-174
File diff suppressed because it is too large
Load Diff
+324
-198
File diff suppressed because it is too large
Load Diff
+306
-186
File diff suppressed because it is too large
Load Diff
+707
-410
File diff suppressed because it is too large
Load Diff
+309
-189
File diff suppressed because it is too large
Load Diff
+307
-187
File diff suppressed because it is too large
Load Diff
+445
-225
File diff suppressed because it is too large
Load Diff
+350
-212
File diff suppressed because it is too large
Load Diff
+339
-204
File diff suppressed because it is too large
Load Diff
+542
-310
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+627
-319
File diff suppressed because it is too large
Load Diff
+324
-198
File diff suppressed because it is too large
Load Diff
+305
-185
File diff suppressed because it is too large
Load Diff
+324
-198
File diff suppressed because it is too large
Load Diff
+327
-201
File diff suppressed because it is too large
Load Diff
+479
-255
File diff suppressed because it is too large
Load Diff
+559
-268
File diff suppressed because it is too large
Load Diff
+323
-197
File diff suppressed because it is too large
Load Diff
+1470
-1198
File diff suppressed because it is too large
Load Diff
+462
-214
File diff suppressed because it is too large
Load Diff
+305
-185
File diff suppressed because it is too large
Load Diff
+304
-184
File diff suppressed because it is too large
Load Diff
+569
-318
File diff suppressed because it is too large
Load Diff
+305
-185
File diff suppressed because it is too large
Load Diff
+317
-191
File diff suppressed because it is too large
Load Diff
+637
-470
File diff suppressed because it is too large
Load Diff
+325
-199
File diff suppressed because it is too large
Load Diff
+342
-204
File diff suppressed because it is too large
Load Diff
+305
-185
File diff suppressed because it is too large
Load Diff
+425
-211
File diff suppressed because it is too large
Load Diff
+345
-204
File diff suppressed because it is too large
Load Diff
+345
-207
File diff suppressed because it is too large
Load Diff
+433
-250
File diff suppressed because it is too large
Load Diff
+328
-199
File diff suppressed because it is too large
Load Diff
+346
-208
File diff suppressed because it is too large
Load Diff
+345
-204
File diff suppressed because it is too large
Load Diff
+321
-195
File diff suppressed because it is too large
Load Diff
+304
-184
File diff suppressed because it is too large
Load Diff
+331
-199
File diff suppressed because it is too large
Load Diff
+304
-184
File diff suppressed because it is too large
Load Diff
+345
-204
File diff suppressed because it is too large
Load Diff
+304
-184
File diff suppressed because it is too large
Load Diff
+304
-184
File diff suppressed because it is too large
Load Diff
+324
-198
File diff suppressed because it is too large
Load Diff
+324
-198
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+326
-200
File diff suppressed because it is too large
Load Diff
+476
-276
File diff suppressed because it is too large
Load Diff
+310
-187
File diff suppressed because it is too large
Load Diff
+22
-19
@@ -1013,6 +1013,7 @@
|
||||
<field name="model"/>
|
||||
<field name="osv_memory"/>
|
||||
<field name="state"/>
|
||||
<field name="modules" colspan="4"/>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Fields">
|
||||
@@ -1027,29 +1028,26 @@
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
<form string="Fields Description">
|
||||
<group colspan="4" col="4">
|
||||
<field name="name" select="1"/>
|
||||
<field colspan="4" name="field_description" select="2"/>
|
||||
</group>
|
||||
<field name="name"/>
|
||||
<field name="field_description"/>
|
||||
<field name="state"/>
|
||||
|
||||
<separator string="Properties" colspan="4"/>
|
||||
<group colspan="2" col="2">
|
||||
<separator string="Field Type" colspan="2"/>
|
||||
<field name="ttype" select="2"/>
|
||||
<field name="ttype"/>
|
||||
<field name="relation" attrs="{'required': [('ttype','in',['many2one','one2many','many2many'])],'readonly': [('ttype','!=','one2many'), ('ttype','!=','many2one'), ('ttype','!=','many2many')]}"/>
|
||||
<field name="relation_field" attrs="{'required': [('ttype','=','one2many')], 'readonly': [('ttype','!=','one2many')]}"/>
|
||||
<field name="selection" attrs="{'required': [('ttype','in',['selection','reference'])], 'readonly': [('ttype','not in',['selection','reference'])]}"/>
|
||||
<field name="size" attrs="{'required': [('ttype','in',['char','reference'])], 'readonly': [('ttype','not in',['char','reference'])]}"/>
|
||||
<field name="state"/>
|
||||
<field name="domain" attrs="{'readonly': [('relation','=','')]}"/>
|
||||
</group>
|
||||
|
||||
<group colspan="2" col="2">
|
||||
<separator string="Properties" colspan="2"/>
|
||||
<field name="required" select="2"/>
|
||||
<field name="readonly" select="2"/>
|
||||
<field name="required"/>
|
||||
<field name="readonly"/>
|
||||
<field name="select_level"/>
|
||||
<field name="translate"/>
|
||||
<field name="on_delete" attrs="{'readonly': [('ttype','!=','many2one')]}"/>
|
||||
<field name="modules"/>
|
||||
</group>
|
||||
<separator string="Groups" colspan="4"/>
|
||||
<field name="groups" colspan="4" nolabel="1"/>
|
||||
@@ -1078,6 +1076,9 @@
|
||||
<page string="Notes">
|
||||
<field colspan="4" name="info" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Views">
|
||||
<field colspan="4" name="view_ids" nolabel="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</form>
|
||||
</field>
|
||||
@@ -1140,7 +1141,7 @@
|
||||
<field name="model_id" attrs="{'readonly': [('state','!=', 'manual')]}"/>
|
||||
<field name="field_description"/>
|
||||
<field name="state"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Properties">
|
||||
<group colspan="2" col="2">
|
||||
@@ -1159,6 +1160,7 @@
|
||||
<field name="select_level"/>
|
||||
<field name="translate"/>
|
||||
<field name="on_delete" attrs="{'readonly': [('ttype','!=','many2one')]}"/>
|
||||
<field name="modules"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Security">
|
||||
@@ -1414,15 +1416,16 @@
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Menu">
|
||||
<group col="8">
|
||||
<group col="6">
|
||||
<field name="name" string="Menu" />
|
||||
<field name="parent_id" colspan="4"/>
|
||||
<field name="parent_id" colspan="2"/>
|
||||
<field name="sequence" colspan="2"/>
|
||||
<field name="complete_name" string="Full Path" colspan="8"/>
|
||||
<field name="action" colspan="4" />
|
||||
<field name="icon" on_change="onchange_icon(icon)" colspan="2"/>
|
||||
<field name="icon_pict" widget="picture" nolabel="1" colspan="2"/>
|
||||
<group col="4" colspan="8" groups="base.group_extended">
|
||||
|
||||
<field name="complete_name" string="Full Path" colspan="2"/>
|
||||
<field name="action" colspan="2" />
|
||||
<field name="icon" colspan="2"/>
|
||||
|
||||
<group col="4" colspan="6" groups="base.group_extended">
|
||||
<field name="web_icon" groups="base.group_extended" />
|
||||
<field name="web_icon_hover" groups="base.group_extended" />
|
||||
<field name="web_icon_data" widget="image" groups="base.group_extended"/>
|
||||
|
||||
@@ -41,6 +41,9 @@ _intervalTypes = {
|
||||
}
|
||||
|
||||
class ir_cron(osv.osv, netsvc.Agent):
|
||||
""" This is the ORM object that periodically executes actions.
|
||||
Note that we use the netsvc.Agent()._logger member.
|
||||
"""
|
||||
_name = "ir.cron"
|
||||
_order = 'name'
|
||||
_columns = {
|
||||
@@ -74,7 +77,7 @@ class ir_cron(osv.osv, netsvc.Agent):
|
||||
try:
|
||||
for this in self.browse(cr, uid, ids, context):
|
||||
str2tuple(this.args)
|
||||
except:
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -90,8 +93,8 @@ class ir_cron(osv.osv, netsvc.Agent):
|
||||
try:
|
||||
f(cr, uid, *args)
|
||||
except Exception, e:
|
||||
self._logger.notifyChannel('timers', netsvc.LOG_ERROR, "Job call of self.pool.get('%s').%s(cr, uid, *%r) failed" % (model, func, args))
|
||||
self._logger.notifyChannel('timers', netsvc.LOG_ERROR, tools.exception_to_unicode(e))
|
||||
cr.rollback()
|
||||
self._logger.exception("Job call of self.pool.get('%s').%s(cr, uid, *%r) failed" % (model, func, args))
|
||||
|
||||
|
||||
def _poolJobs(self, db_name, check=False):
|
||||
@@ -135,9 +138,7 @@ class ir_cron(osv.osv, netsvc.Agent):
|
||||
self.setAlarm(self._poolJobs, next_call, db_name, db_name)
|
||||
|
||||
except Exception, ex:
|
||||
logger = netsvc.Logger()
|
||||
logger.notifyChannel('cron', netsvc.LOG_WARNING,
|
||||
'Exception in cron:'+str(ex))
|
||||
self._logger.warning('Exception in cron:', exc_info=True)
|
||||
|
||||
finally:
|
||||
cr.commit()
|
||||
|
||||
+174
-29
@@ -42,6 +42,20 @@ def _get_fields_type(self, cr, uid, context=None):
|
||||
field_types.remove(types)
|
||||
return field_types
|
||||
|
||||
def _in_modules(self, cr, uid, ids, field_name, arg, context=None):
|
||||
#pseudo-method used by fields.function in ir.model/ir.model.fields
|
||||
module_pool = self.pool.get("ir.module.module")
|
||||
installed_module_ids = module_pool.search(cr, uid, [('state','=','installed')])
|
||||
installed_module_names = module_pool.read(cr, uid, installed_module_ids, ['name'], context=context)
|
||||
installed_modules = set(x['name'] for x in installed_module_names)
|
||||
|
||||
result = {}
|
||||
xml_ids = osv.osv._get_xml_ids(self, cr, uid, ids)
|
||||
for k,v in xml_ids.iteritems():
|
||||
result[k] = ', '.join(sorted(installed_modules & set(xml_id.split('.')[0] for xml_id in v)))
|
||||
return result
|
||||
|
||||
|
||||
class ir_model(osv.osv):
|
||||
_name = 'ir.model'
|
||||
_description = "Objects"
|
||||
@@ -65,6 +79,12 @@ class ir_model(osv.osv):
|
||||
is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context)
|
||||
return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])]
|
||||
|
||||
def _view_ids(self, cr, uid, ids, field_name, arg, context=None):
|
||||
models = self.browse(cr, uid, ids)
|
||||
res = {}
|
||||
for model in models:
|
||||
res[model.id] = self.pool.get("ir.ui.view").search(cr, uid, [('model', '=', model.model)])
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Object Name', size=64, translate=True, required=True),
|
||||
@@ -75,7 +95,9 @@ class ir_model(osv.osv):
|
||||
'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'),
|
||||
'osv_memory': fields.function(_is_osv_memory, method=True, string='In-memory model', type='boolean',
|
||||
fnct_search=_search_osv_memory,
|
||||
help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)")
|
||||
help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)"),
|
||||
'modules': fields.function(_in_modules, method=True, type='char', size=128, string='In modules', help='List of modules in which the object is defined or inherited'),
|
||||
'view_ids': fields.function(_view_ids, method=True, type='one2many', obj='ir.ui.view', string='Views'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
@@ -152,6 +174,7 @@ ir_model()
|
||||
class ir_model_fields(osv.osv):
|
||||
_name = 'ir.model.fields'
|
||||
_description = "Fields"
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True, size=64, select=1),
|
||||
'model': fields.char('Object Name', size=64, required=True, select=1,
|
||||
@@ -180,39 +203,45 @@ class ir_model_fields(osv.osv):
|
||||
'groups': fields.many2many('res.groups', 'ir_model_fields_group_rel', 'field_id', 'group_id', 'Groups'),
|
||||
'view_load': fields.boolean('View Auto-Load'),
|
||||
'selectable': fields.boolean('Selectable'),
|
||||
'modules': fields.function(_in_modules, method=True, type='char', size=128, string='In modules', help='List of modules in which the field is defined'),
|
||||
}
|
||||
_rec_name='field_description'
|
||||
_defaults = {
|
||||
'view_load': lambda *a: 0,
|
||||
'selection': lambda *a: "[('key','label')]",
|
||||
'domain': lambda *a: "[]",
|
||||
'name': lambda *a: 'x_',
|
||||
'view_load': 0,
|
||||
'selection': "",
|
||||
'domain': "[]",
|
||||
'name': 'x_',
|
||||
'state': lambda self,cr,uid,ctx={}: (ctx and ctx.get('manual',False)) and 'manual' or 'base',
|
||||
'on_delete': lambda *a: 'set null',
|
||||
'select_level': lambda *a: '0',
|
||||
'size': lambda *a: 64,
|
||||
'field_description': lambda *a: '',
|
||||
'selectable': lambda *a: 1,
|
||||
'on_delete': 'set null',
|
||||
'select_level': '0',
|
||||
'size': 64,
|
||||
'field_description': '',
|
||||
'selectable': 1,
|
||||
}
|
||||
_order = "name"
|
||||
|
||||
def _check_selection(self, cr, uid, ids, context=None):
|
||||
selection_field = self.browse(cr, uid, ids[0], context=context)
|
||||
def _check_selection(self, cr, uid, selection, context=None):
|
||||
try:
|
||||
selection_list = eval(selection_field.selection)
|
||||
selection_list = eval(selection)
|
||||
except Exception:
|
||||
logging.getLogger('ir.model').error('Invalid selection list definition for fields.selection %s', selection_field.name , exc_info=True)
|
||||
return False
|
||||
if not (isinstance(selection_list, list) and selection_list):
|
||||
return False
|
||||
for selection_item in selection_list:
|
||||
if not (isinstance(selection_item, (tuple,list)) and len(selection_item) == 2):
|
||||
return False
|
||||
return True
|
||||
logging.getLogger('ir.model').warning('Invalid selection list definition for fields.selection', exc_info=True)
|
||||
raise except_orm(_('Error'),
|
||||
_("The Selection Options expression is not a valid Pythonic expression." \
|
||||
"Please provide an expression in the [('key','Label'), ...] format."))
|
||||
|
||||
_constraints = [
|
||||
(_check_selection, "Wrong list of values specified for a field of type selection, it should be written as [('key','value')]", ['selection'])
|
||||
]
|
||||
check = True
|
||||
if not (isinstance(selection_list, list) and selection_list):
|
||||
check = False
|
||||
else:
|
||||
for item in selection_list:
|
||||
if not (isinstance(item, (tuple,list)) and len(item) == 2):
|
||||
check = False
|
||||
break
|
||||
|
||||
if not check:
|
||||
raise except_orm(_('Error'),
|
||||
_("The Selection Options expression is must be in the [('key','Label'), ...] format!"))
|
||||
return True
|
||||
|
||||
def _size_gt_zero_msg(self, cr, user, ids, context=None):
|
||||
return _('Size of the field can never be less than 1 !')
|
||||
@@ -220,6 +249,7 @@ class ir_model_fields(osv.osv):
|
||||
_sql_constraints = [
|
||||
('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
|
||||
]
|
||||
|
||||
def unlink(self, cr, user, ids, context=None):
|
||||
for field in self.browse(cr, user, ids, context):
|
||||
if field.state <> 'manual':
|
||||
@@ -239,6 +269,10 @@ class ir_model_fields(osv.osv):
|
||||
context = {}
|
||||
if context and context.get('manual',False):
|
||||
vals['state'] = 'manual'
|
||||
if vals.get('ttype', False) == 'selection':
|
||||
if not vals.get('selection',False):
|
||||
raise except_orm(_('Error'), _('For selection fields, the Selection Options must be given!'))
|
||||
self._check_selection(cr, user, vals['selection'], context=context)
|
||||
res = super(ir_model_fields,self).create(cr, user, vals, context)
|
||||
if vals.get('state','base') == 'manual':
|
||||
if not vals['name'].startswith('x_'):
|
||||
@@ -256,13 +290,120 @@ class ir_model_fields(osv.osv):
|
||||
|
||||
return res
|
||||
|
||||
def write(self, cr, user, ids, vals, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if context and context.get('manual',False):
|
||||
vals['state'] = 'manual'
|
||||
|
||||
column_rename = None # if set, *one* column can be renamed here
|
||||
obj = None
|
||||
models_patch = {} # structs of (obj, [(field, prop, change_to),..])
|
||||
# data to be updated on the orm model
|
||||
|
||||
# static table of properties
|
||||
model_props = [ # (our-name, fields.prop, set_fn)
|
||||
('field_description', 'string', lambda a: a),
|
||||
('required', 'required', bool),
|
||||
('readonly', 'readonly', bool),
|
||||
('domain', '_domain', lambda a: a),
|
||||
('size', 'size', int),
|
||||
('on_delete', 'ondelete', str),
|
||||
('translate', 'translate', bool),
|
||||
('view_load', 'view_load', bool),
|
||||
('selectable', 'selectable', bool),
|
||||
('select_level', 'select', int),
|
||||
('selection', 'selection', eval),
|
||||
]
|
||||
|
||||
if vals and ids:
|
||||
checked_selection = False # need only check it once, so defer
|
||||
|
||||
for item in self.browse(cr, user, ids, context=context):
|
||||
if not (obj and obj._name == item.model):
|
||||
obj = self.pool.get(item.model)
|
||||
|
||||
if item.state != 'manual':
|
||||
raise except_orm(_('Error!'),
|
||||
_('Properties of base fields cannot be altered in this manner! '
|
||||
'Please modify them through Python code, '
|
||||
'preferably through a custom addon!'))
|
||||
|
||||
if item.ttype == 'selection' and 'selection' in vals \
|
||||
and not checked_selection:
|
||||
self._check_selection(cr, user, vals['selection'], context=context)
|
||||
checked_selection = True
|
||||
|
||||
final_name = item.name
|
||||
if 'name' in vals and vals['name'] != item.name:
|
||||
# We need to rename the column
|
||||
if column_rename:
|
||||
raise except_orm(_('Error!'), _('Can only rename one column at a time!'))
|
||||
if vals['name'] in obj._columns:
|
||||
raise except_orm(_('Error!'), _('Cannot rename column to %s, because that column already exists!') % vals['name'])
|
||||
if vals.get('state', 'base') == 'manual' and not vals['name'].startswith('x_'):
|
||||
raise except_orm(_('Error!'), _('New column name must still start with x_ , because it is a custom field!'))
|
||||
if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']:
|
||||
raise ValueError('Invalid character in column name')
|
||||
column_rename = (obj, (obj._table, item.name, vals['name']))
|
||||
final_name = vals['name']
|
||||
|
||||
if 'model_id' in vals and vals['model_id'] != item.model_id:
|
||||
raise except_orm(_("Error!"), _("Changing the model of a field is forbidden!"))
|
||||
|
||||
if 'ttype' in vals and vals['ttype'] != item.ttype:
|
||||
raise except_orm(_("Error!"), _("Changing the type of a column is not yet supported. "
|
||||
"Please drop it and create it again!"))
|
||||
|
||||
# We don't check the 'state', because it might come from the context
|
||||
# (thus be set for multiple fields) and will be ignored anyway.
|
||||
if obj:
|
||||
models_patch.setdefault(obj._name, (obj,[]))
|
||||
# find out which properties (per model) we need to update
|
||||
for field_name, field_property, set_fn in model_props:
|
||||
if field_name in vals:
|
||||
property_value = set_fn(vals[field_name])
|
||||
if getattr(obj._columns[item.name], field_property) != property_value:
|
||||
models_patch[obj._name][1].append((final_name, field_property, property_value))
|
||||
# our dict is ready here, but no properties are changed so far
|
||||
|
||||
# These shall never be written (modified)
|
||||
for column_name in ('model_id', 'model', 'state'):
|
||||
if column_name in vals:
|
||||
del vals[column_name]
|
||||
|
||||
res = super(ir_model_fields,self).write(cr, user, ids, vals, context=context)
|
||||
|
||||
if column_rename:
|
||||
cr.execute('ALTER TABLE "%s" RENAME COLUMN "%s" TO "%s"' % column_rename[1])
|
||||
# This is VERY risky, but let us have this feature:
|
||||
# we want to change the key of column in obj._columns dict
|
||||
col = column_rename[0]._columns.pop(column_rename[1][1]) # take object out, w/o copy
|
||||
column_rename[0]._columns[column_rename[1][2]] = col
|
||||
|
||||
if models_patch:
|
||||
# We have to update _columns of the model(s) and then call their
|
||||
# _auto_init to sync the db with the model. Hopefully, since write()
|
||||
# was called earlier, they will be in-sync before the _auto_init.
|
||||
# Anything we don't update in _columns now will be reset from
|
||||
# the model into ir.model.fields (db).
|
||||
ctx = context.copy()
|
||||
ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
|
||||
|
||||
for model_key, patch_struct in models_patch.items():
|
||||
obj = patch_struct[0]
|
||||
for col_name, col_prop, val in patch_struct[1]:
|
||||
setattr(obj._columns[col_name], col_prop, val)
|
||||
obj._auto_init(cr, ctx)
|
||||
return res
|
||||
|
||||
ir_model_fields()
|
||||
|
||||
class ir_model_access(osv.osv):
|
||||
_name = 'ir.model.access'
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True, select=True),
|
||||
'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True),
|
||||
'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)], select=True, ondelete='cascade'),
|
||||
'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade', select=True),
|
||||
'perm_read': fields.boolean('Read Access'),
|
||||
'perm_write': fields.boolean('Write Access'),
|
||||
@@ -437,10 +578,14 @@ class ir_model_data(osv.osv):
|
||||
|
||||
def __init__(self, pool, cr):
|
||||
osv.osv.__init__(self, pool, cr)
|
||||
self.loads = {}
|
||||
self.doinit = True
|
||||
self.unlink_mark = {}
|
||||
|
||||
# also stored in pool to avoid being discarded along with this osv instance
|
||||
if getattr(pool, 'model_data_reference_ids', None) is None:
|
||||
self.pool.model_data_reference_ids = {}
|
||||
self.loads = self.pool.model_data_reference_ids
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
super(ir_model_data, self)._auto_init(cr, context)
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_model_data_module_name_index\'')
|
||||
@@ -531,7 +676,7 @@ class ir_model_data(osv.osv):
|
||||
'name': xml_id + '_' + table.replace('.', '_'),
|
||||
'model': table,
|
||||
'module': module,
|
||||
'res_id': inherit_id,
|
||||
'res_id': inherit_id.id,
|
||||
'noupdate': noupdate,
|
||||
},context=context)
|
||||
else:
|
||||
@@ -553,7 +698,7 @@ class ir_model_data(osv.osv):
|
||||
'name': xml_id + '_' + table.replace('.', '_'),
|
||||
'model': table,
|
||||
'module': module,
|
||||
'res_id': inherit_id,
|
||||
'res_id': inherit_id.id,
|
||||
'noupdate': noupdate,
|
||||
},context=context)
|
||||
if xml_id:
|
||||
@@ -646,7 +791,7 @@ class ir_model_data(osv.osv):
|
||||
cr.commit()
|
||||
except Exception:
|
||||
cr.rollback()
|
||||
self.__logger.exception(
|
||||
self.__logger.warn(
|
||||
'Could not delete id: %d of model %s\nThere '
|
||||
'should be some relation that points to this '
|
||||
'resource\nYou should manually fix this and '
|
||||
|
||||
@@ -52,32 +52,6 @@ class ir_ui_menu(osv.osv):
|
||||
# radical but this doesn't frequently happen
|
||||
self._cache = {}
|
||||
|
||||
def create_shortcut(self, cr, uid, values, context=None):
|
||||
dataobj = self.pool.get('ir.model.data')
|
||||
if context is None:
|
||||
context = {}
|
||||
new_context = context.copy()
|
||||
for key in context:
|
||||
if key.startswith('default_'):
|
||||
del new_context[key]
|
||||
|
||||
menu_id = dataobj._get_id(cr, uid, 'base', 'menu_administration_shortcut', new_context)
|
||||
shortcut_menu_id = int(dataobj.read(cr, uid, menu_id, ['res_id'], new_context)['res_id'])
|
||||
action_id = self.pool.get('ir.actions.act_window').create(cr, uid, values, new_context)
|
||||
menu_data = {'name':values['name'],
|
||||
'sequence':10,
|
||||
'action':'ir.actions.act_window,'+str(action_id),
|
||||
'parent_id':shortcut_menu_id,
|
||||
'icon':'STOCK_JUSTIFY_FILL'}
|
||||
menu_id = self.pool.get('ir.ui.menu').create(cr, 1, menu_data)
|
||||
sc_data= {'name':values['name'], 'sequence': 1,'res_id': menu_id }
|
||||
sc_menu_id = self.pool.get('ir.ui.view_sc').create(cr, uid, sc_data, new_context)
|
||||
|
||||
user_groups = set(self.pool.get('res.users').read(cr, 1, uid, ['groups_id'])['groups_id'])
|
||||
key = (cr.dbname, shortcut_menu_id, tuple(user_groups))
|
||||
self._cache[key] = True
|
||||
return action_id
|
||||
|
||||
def _filter_visible_menus(self, cr, uid, ids, context=None):
|
||||
"""Filters the give menu ids to only keep the menu items that should be
|
||||
visible in the menu hierarchy of the current user.
|
||||
|
||||
@@ -181,8 +181,8 @@ view()
|
||||
class view_sc(osv.osv):
|
||||
_name = 'ir.ui.view_sc'
|
||||
_columns = {
|
||||
'name': fields.char('Shortcut Name', size=64, required=True),
|
||||
'res_id': fields.many2one('ir.ui.menu','Resource Ref.', ondelete='cascade'),
|
||||
'name': fields.char('Shortcut Name', size=64), # Kept for backwards compatibility only - resource name used instead (translatable)
|
||||
'res_id': fields.integer('Resource Ref.', help="Reference of the target resource, whose model/table depends on the 'Resource Name' field."),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'user_id': fields.many2one('res.users', 'User Ref.', required=True, ondelete='cascade', select=True),
|
||||
'resource': fields.char('Resource Name', size=64, required=True, select=True)
|
||||
@@ -194,9 +194,13 @@ class view_sc(osv.osv):
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX ir_ui_view_sc_user_id_resource ON ir_ui_view_sc (user_id, resource)')
|
||||
|
||||
def get_sc(self, cr, uid, user_id, model='ir.ui.menu', context={}):
|
||||
def get_sc(self, cr, uid, user_id, model='ir.ui.menu', context=None):
|
||||
ids = self.search(cr, uid, [('user_id','=',user_id),('resource','=',model)], context=context)
|
||||
return self.read(cr, uid, ids, ['res_id','name'], context=context)
|
||||
results = self.read(cr, uid, ids, ['res_id'], context=context)
|
||||
name_map = dict(self.pool.get(model).name_get(cr, uid, [x['res_id'] for x in results], context=context))
|
||||
for result in results:
|
||||
result.update(name=name_map[result['res_id']])
|
||||
return results
|
||||
|
||||
_order = 'sequence,name'
|
||||
_defaults = {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<report id="report_ir_model_overview"
|
||||
string="Model Overview"
|
||||
model="ir.model"
|
||||
name="ir.model.overview"
|
||||
report_type="sxw"
|
||||
file="base/ir/report/modeloverview.sxw"
|
||||
header="False"
|
||||
/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
Binary file not shown.
@@ -38,7 +38,7 @@ def graph_get(cr, graph, wkf_ids, nested=False, workitem={}):
|
||||
cr.execute('select * from wkf where id=%s', (n['subflow_id'],))
|
||||
wkfinfo = cr.dictfetchone()
|
||||
graph2 = pydot.Cluster('subflow'+str(n['subflow_id']), fontsize='12', label = """\"Subflow: %s\\nOSV: %s\"""" % ( n['name'], wkfinfo['osv']) )
|
||||
(s1,s2) = graph_get(cr, graph2, n['subflow_id'], nested,workitem)
|
||||
(s1,s2) = graph_get(cr, graph2, [n['subflow_id']], nested,workitem)
|
||||
graph.add_subgraph(graph2)
|
||||
actfrom[n['id']] = s2
|
||||
actto[n['id']] = s1
|
||||
|
||||
@@ -19,11 +19,15 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import base64
|
||||
import cStringIO
|
||||
import imp
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import StringIO
|
||||
import urllib
|
||||
import zipfile
|
||||
import zipimport
|
||||
|
||||
import addons
|
||||
@@ -554,6 +558,58 @@ class module(osv.osv):
|
||||
else:
|
||||
self._web_dependencies(
|
||||
cr, uid, parent, context=context)
|
||||
|
||||
def _translations_subdir(self, module):
|
||||
""" Returns the path to the subdirectory holding translations for the
|
||||
module files, or None if it can't find one
|
||||
|
||||
:param module: a module object
|
||||
:type module: browse(ir.module.module)
|
||||
"""
|
||||
subdir = addons.get_module_resource(module.name, 'po')
|
||||
if subdir: return subdir
|
||||
# old naming convention
|
||||
subdir = addons.get_module_resource(module.name, 'i18n')
|
||||
if subdir: return subdir
|
||||
return None
|
||||
|
||||
def _add_translations(self, module, web_data):
|
||||
""" Adds translation data to a zipped web module
|
||||
|
||||
:param module: a module descriptor
|
||||
:type module: browse(ir.module.module)
|
||||
:param web_data: zipped data of a web module
|
||||
:type web_data: bytes
|
||||
"""
|
||||
# cStringIO.StringIO is either read or write, not r/w
|
||||
web_zip = StringIO.StringIO(web_data)
|
||||
web_archive = zipfile.ZipFile(web_zip, 'a')
|
||||
|
||||
# get the contents of the i18n or po folder and move them to the
|
||||
# po/messages subdirectory of the web module.
|
||||
# The POT file will be incorrectly named, but that should not
|
||||
# matter since the web client is not going to use it, only the PO
|
||||
# files.
|
||||
translations_file = cStringIO.StringIO(
|
||||
addons.zip_directory(self._translations_subdir(module), False))
|
||||
translations_archive = zipfile.ZipFile(translations_file)
|
||||
|
||||
for path in translations_archive.namelist():
|
||||
web_path = os.path.join(
|
||||
'web', 'po', 'messages', os.path.basename(path))
|
||||
web_archive.writestr(
|
||||
web_path,
|
||||
translations_archive.read(path))
|
||||
|
||||
translations_archive.close()
|
||||
translations_file.close()
|
||||
|
||||
web_archive.close()
|
||||
try:
|
||||
return web_zip.getvalue()
|
||||
finally:
|
||||
web_zip.close()
|
||||
|
||||
def get_web(self, cr, uid, names, context=None):
|
||||
""" get_web(cr, uid, [module_name], context) -> [{name, depends, content}]
|
||||
|
||||
@@ -568,15 +624,21 @@ class module(osv.osv):
|
||||
if not modules: return []
|
||||
self.__logger.info('Sending web content of modules %s '
|
||||
'to web client', names)
|
||||
return [
|
||||
{'name': module.name,
|
||||
'version': module.installed_version,
|
||||
'depends': list(self._web_dependencies(
|
||||
cr, uid, module, context=context)),
|
||||
'content': addons.zip_directory(
|
||||
addons.get_module_resource(module.name, 'web'))}
|
||||
for module in modules
|
||||
]
|
||||
|
||||
modules_data = []
|
||||
for module in modules:
|
||||
web_data = addons.zip_directory(
|
||||
addons.get_module_resource(module.name, 'web'), False)
|
||||
if self._translations_subdir(module):
|
||||
web_data = self._add_translations(module, web_data)
|
||||
modules_data.append({
|
||||
'name': module.name,
|
||||
'version': module.installed_version,
|
||||
'depends': list(self._web_dependencies(
|
||||
cr, uid, module, context=context)),
|
||||
'content': base64.encodestring(web_data)
|
||||
})
|
||||
return modules_data
|
||||
|
||||
module()
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class base_language_export(osv.osv_memory):
|
||||
mods = map(lambda m: m.name, this.modules) or ['all']
|
||||
mods.sort()
|
||||
buf=cStringIO.StringIO()
|
||||
tools.trans_export(this.lang, mods, buf, this.format, dbname=cr.dbname)
|
||||
tools.trans_export(this.lang, mods, buf, this.format, cr)
|
||||
if this.format == 'csv':
|
||||
this.advice = _("Save this document to a .CSV file and open it with your favourite spreadsheet software. The file encoding is UTF-8. You have to translate the latest column before reimporting it.")
|
||||
elif this.format == 'po':
|
||||
@@ -86,4 +86,4 @@ class base_language_export(osv.osv_memory):
|
||||
}
|
||||
base_language_export()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import tools
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
|
||||
class base_language_install(osv.osv_memory):
|
||||
""" Install Language"""
|
||||
@@ -50,7 +51,18 @@ class base_language_install(osv.osv_memory):
|
||||
context = {'overwrite': True}
|
||||
modobj.update_translations(cr, uid, mids, lang, context or {})
|
||||
self.write(cr, uid, ids, {'state': 'done'}, context=context)
|
||||
return False
|
||||
return {
|
||||
'name': _('Language Pack'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'view_id': False,
|
||||
'res_model': 'base.language.install',
|
||||
'domain': [],
|
||||
'context': dict(context, active_ids=ids),
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
'res_id': ids and ids[0] or False,
|
||||
}
|
||||
base_language_install()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Load Official Translation">
|
||||
<group col="8">
|
||||
<group colspan="3">
|
||||
<group colspan="4" col="8">
|
||||
<group colspan="1">
|
||||
<field name="config_logo" widget="image" width="220" height="130" nolabel="1" colspan="1"/>
|
||||
<newline/>
|
||||
<label colspan="4" width="220" string="This wizard helps you add a new language to your OpenERP system. After loading a new language it becomes available as default interface language for users and partners."/>
|
||||
@@ -17,8 +17,8 @@
|
||||
<label colspan="4" width="220" string="Please be patient, this operation may take a few minutes (depending on the number of modules currently installed)..."/>
|
||||
<field name="state" invisible="1"/>
|
||||
</group>
|
||||
<separator orientation="vertical" rowspan="15"/>
|
||||
<group colspan="4">
|
||||
<separator orientation="vertical" colspan="1" rowspan="12"/>
|
||||
<group colspan="6">
|
||||
<separator string="Load an Official Translation" colspan="4"/>
|
||||
<group states="init" colspan="4">
|
||||
<field name="lang" colspan="4"/>
|
||||
@@ -29,18 +29,18 @@
|
||||
You must change the preferences of the user and open a new menu to view the changes." colspan="4"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="8" col="8" states="init">
|
||||
<separator string="" colspan="8"/>
|
||||
<label colspan="6" width="220"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="lang_install" string="Load" type="object" icon="gtk-ok"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel" colspan="1"/>
|
||||
<button name="lang_install" string="Load" type="object" icon="gtk-ok" colspan="1"/>
|
||||
</group>
|
||||
<group colspan="8" col="8" states="done">
|
||||
<separator string="" colspan="8"/>
|
||||
<label colspan="7" width="220"/>
|
||||
<button special="cancel" string="Close" icon="gtk-ok"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<field name="code">action = obj.start()</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Start Configuration"
|
||||
<menuitem name="Reconfigure"
|
||||
action="action_start_configurator"
|
||||
id="menu_view_base_module_configuration" parent="base.menu_config"
|
||||
type="server" icon="STOCK_EXECUTE" sequence="100" />
|
||||
@@ -38,12 +38,6 @@
|
||||
<field name="key">action</field>
|
||||
<field name="model">ir.actions.todo</field>
|
||||
</record>
|
||||
<record id="ir_ui_view_sc_configuration" model="ir.ui.view_sc">
|
||||
<field name="name">Reconfigure</field>
|
||||
<field name="resource">ir.ui.menu</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="res_id" ref="menu_view_base_module_configuration"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="ir_ui_view_sc_configuration" model="ir.ui.view_sc">
|
||||
<field name="name">Reconfigure</field>
|
||||
<field name="resource">ir.ui.menu</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="res_id" ref="menu_view_base_module_configuration"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -121,8 +121,8 @@ class publisher_warranty_contract(osv.osv):
|
||||
raise
|
||||
except Exception:
|
||||
_logger.warning("Error sending problem report", exc_info=1)
|
||||
raise osv.except_osv("Connection error", "An error occured during the connection " +
|
||||
"with the publisher warranty server.")
|
||||
raise osv.except_osv(_("Error"),
|
||||
_("Error during communication with the publisher warranty server."))
|
||||
|
||||
return True
|
||||
|
||||
@@ -142,7 +142,7 @@ class publisher_warranty_contract(osv.osv):
|
||||
validated2 = contract.state != "unvalidated"
|
||||
if not validated and not validated2:
|
||||
raise osv.except_osv(_("Contract validation error"),
|
||||
_("Please check your publisher warranty contract name and validity."))
|
||||
_("Please verify your publisher warranty serial number and validity."))
|
||||
return True
|
||||
|
||||
def get_logs(self, cr, uid, ids, cron_mode=True, context=None):
|
||||
@@ -165,14 +165,26 @@ class publisher_warranty_contract(osv.osv):
|
||||
contracts = result["contracts"]
|
||||
for contract in contracts:
|
||||
c_id = self.search(cr, uid, [("name","=",contract)])[0]
|
||||
date_from = contracts[contract][0]
|
||||
date_to = contracts[contract][1]
|
||||
state = contracts[contract][2]
|
||||
self.write(cr, uid, c_id, {
|
||||
"date_start": date_from,
|
||||
"date_stop": date_to,
|
||||
"state": state,
|
||||
})
|
||||
# for backward compatibility
|
||||
if type(contracts[contract]) == tuple:
|
||||
self.write(cr, uid, c_id, {
|
||||
"date_start": contracts[contract][0],
|
||||
"date_stop": contracts[contract][1],
|
||||
"state": contracts[contract][2],
|
||||
"check_support": False,
|
||||
"check_opw": False,
|
||||
"kind": "",
|
||||
})
|
||||
else:
|
||||
self.write(cr, uid, c_id, {
|
||||
"date_start": contracts[contract]["date_from"],
|
||||
"date_stop": contracts[contract]["date_to"],
|
||||
"state": contracts[contract]["state"],
|
||||
"check_support": contracts[contract]["check_support"],
|
||||
"check_opw": contracts[contract]["check_opw"],
|
||||
"kind": contracts[contract]["kind"],
|
||||
})
|
||||
|
||||
|
||||
limit_date = (datetime.datetime.now() - _PREVIOUS_LOG_CHECK).strftime(misc.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
for message in result["messages"]:
|
||||
@@ -220,12 +232,14 @@ class publisher_warranty_contract(osv.osv):
|
||||
return True
|
||||
|
||||
_columns = {
|
||||
'name' : fields.char('Serial Key', size=384, required=True),
|
||||
'name' : fields.char('Serial Key', size=384, required=True, help="Your OpenERP Publisher's Warranty Contract unique key, also called serial number."),
|
||||
'date_start' : fields.date('Starting Date', readonly=True),
|
||||
'date_stop' : fields.date('Ending Date', readonly=True),
|
||||
'state' : fields.selection([('unvalidated', 'Unvalidated'), ('valid', 'Valid')
|
||||
, ('terminated', 'Terminated'), ('canceled', 'Canceled')], string="State", readonly=True),
|
||||
'kind' : fields.char('Kind', size=64, readonly=True),
|
||||
"check_support": fields.boolean("Support Level 1", readonly=True),
|
||||
"check_opw": fields.boolean("OPW", readonly=True, help="Checked if this is an OpenERP Publisher's Warranty contract (versus older contract types"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
@@ -233,7 +247,7 @@ class publisher_warranty_contract(osv.osv):
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('uniq_name', 'unique(name)', "Your publisher warranty contract is already subscribed in the system !")
|
||||
('uniq_name', 'unique(name)', "That contract is already registered in the system.")
|
||||
]
|
||||
|
||||
publisher_warranty_contract()
|
||||
@@ -260,7 +274,7 @@ class publisher_warranty_contract_wizard(osv.osv_memory):
|
||||
_inherit = "ir.wizard.screen"
|
||||
|
||||
_columns = {
|
||||
'name' : fields.char('Serial Key', size=256, required=True ),
|
||||
'name' : fields.char('Serial Key', size=256, required=True, help="Your OpenERP Publisher's Warranty Contract unique key, also called serial number."),
|
||||
'state' : fields.selection([("draft", "Draft"), ("finished", "Finished")])
|
||||
}
|
||||
|
||||
@@ -289,6 +303,7 @@ class publisher_warranty_contract_wizard(osv.osv_memory):
|
||||
|
||||
self.write(cr, uid, ids, {"state": "finished"})
|
||||
|
||||
# We should return an action ?
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -22,19 +22,21 @@
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Publisher Warranty Contract">
|
||||
<group col="2" colspan="4">
|
||||
<group col="3" colspan="4">
|
||||
<group col="2">
|
||||
<field name="name"/>
|
||||
</group>
|
||||
<group col="2">
|
||||
<field name="date_start"/>
|
||||
<field name="date_stop"/>
|
||||
<field name="check_support"/>
|
||||
</group>
|
||||
<group col="2">
|
||||
<field name="state"/>
|
||||
<field name="kind"/>
|
||||
<field name="check_opw"/>
|
||||
</group>
|
||||
<group col="2">
|
||||
<group col="2" colspan="3">
|
||||
<button name="check_validity" string="Validate" type="object"
|
||||
attrs="{'invisible':[('state','in',['valid', 'terminated', 'canceled'])]}"/>
|
||||
<button name="check_validity" string="Refresh Validation Dates" type="object"
|
||||
@@ -92,38 +94,38 @@
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Register a Contract">
|
||||
<group col="8">
|
||||
<group colspan="3" col="1">
|
||||
<group colspan="4" col="8">
|
||||
<group colspan="1">
|
||||
<field name="config_logo" widget="image" width="220" height="130" nolabel="1" colspan="1"/>
|
||||
<newline/>
|
||||
<label width="220" string="This wizard helps you register a publisher warranty contract in your OpenERP system. After the contract has been registered, you will be able to send issues directly to OpenERP."/>
|
||||
<label width="220"/>
|
||||
<label width="220" string=""/>
|
||||
<label colspan="4" width="220" string="This wizard helps you register a publisher warranty contract in your OpenERP system. After the contract has been registered, you will be able to send issues directly to OpenERP."/>
|
||||
<label colspan="4" width="220"/>
|
||||
<label colspan="4" width="220" string=""/>
|
||||
<field name="state" invisible="1"/>
|
||||
</group>
|
||||
<separator orientation="vertical" rowspan="5"/>
|
||||
<group colspan="4">
|
||||
<separator orientation="vertical" rowspan="12" colspan="1"/>
|
||||
<group colspan="6">
|
||||
<separator string="Register a Contract" colspan="4"/>
|
||||
<group states="draft" col="4">
|
||||
<group states="draft" colspan="4">
|
||||
<label string="Please enter the serial key provided in your contract document:" colspan="4"/>
|
||||
<field name="name" colspan="4"/>
|
||||
</group>
|
||||
<group states="finished" col="4">
|
||||
<group states="finished" colspan="4">
|
||||
<label string="Publisher warranty contract successfully registered!" colspan="4"/>
|
||||
</group>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="8" col="8" states="draft">
|
||||
<separator string="" colspan="8"/>
|
||||
<label colspan="6" width="220"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
|
||||
<button name="action_validate" string="Register" type="object" icon="gtk-apply"/>
|
||||
<button special="cancel" string="Cancel" icon="gtk-cancel" colspan="1"/>
|
||||
<button name="action_validate" string="Register" type="object" icon="gtk-apply" colspan="1"/>
|
||||
</group>
|
||||
<group colspan="8" col="8" states="finished">
|
||||
<separator string="" colspan="8"/>
|
||||
<label colspan="6" width="220"/>
|
||||
<label colspan="7" width="220"/>
|
||||
<button special="cancel" string="Close" icon="gtk-ok"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -113,9 +113,9 @@ class res_partner(osv.osv):
|
||||
'name': fields.char('Name', size=128, required=True, select=True),
|
||||
'date': fields.date('Date', select=1),
|
||||
'title': fields.many2one('res.partner.title','Partner Form'),
|
||||
'parent_id': fields.many2one('res.partner','Parent Partner', select=2),
|
||||
'parent_id': fields.many2one('res.partner','Parent Partner'),
|
||||
'child_ids': fields.one2many('res.partner', 'parent_id', 'Partner Ref.'),
|
||||
'ref': fields.char('Reference', size=64),
|
||||
'ref': fields.char('Reference', size=64, select=1),
|
||||
'lang': fields.selection(_lang_get, 'Language', size=5, help="If the selected language is loaded in the system, all documents related to this partner will be printed in this language. If not, it will be english."),
|
||||
'user_id': fields.many2one('res.users', 'Salesman', help='The internal user that is in charge of communicating with this partner if any.'),
|
||||
'vat': fields.char('VAT',size=32 ,help="Value Added Tax number. Check the box if the partner is subjected to the VAT. Used by the VAT legal statement."),
|
||||
@@ -273,7 +273,7 @@ res_partner()
|
||||
class res_partner_address(osv.osv):
|
||||
_description ='Partner Addresses'
|
||||
_name = 'res.partner.address'
|
||||
_order = 'name'
|
||||
_order = 'type, name'
|
||||
_columns = {
|
||||
'partner_id': fields.many2one('res.partner', 'Partner Name', ondelete='set null', select=True, help="Keep empty for a private address, not related to partner."),
|
||||
'type': fields.selection( [ ('default','Default'),('invoice','Invoice'), ('delivery','Delivery'), ('contact','Contact'), ('other','Other') ],'Address Type', help="Used to select automatically the right address according to the context in sales and purchases documents."),
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="ir_ui_view_sc_partner0" model="ir.ui.view_sc">
|
||||
<field name="name">Customers</field>
|
||||
<field name="resource">ir.ui.menu</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="res_id" ref="menu_partner_form"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -108,7 +108,7 @@
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">res.partner.address</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="context">{"search_default_customer":1,"search_default_partner_id":self}</field>
|
||||
<field name="context">{"search_default_customer":1}</field>
|
||||
<field name="search_view_id" ref="view_res_partner_address_filter"/>
|
||||
<field name="help">Customers (also called Partners in other areas of the system) helps you manage your address book of companies whether they are prospects, customers and/or suppliers. The partner form allows you to track and record all the necessary information to interact with your partners from the company address to their contacts as well as pricelists, and much more. If you installed the CRM, with the history tab, you can track all the interactions with a partner such as opportunities, emails, or sales orders issued.</field>
|
||||
</record>
|
||||
@@ -590,12 +590,5 @@
|
||||
groups="base.group_extended"
|
||||
/>
|
||||
|
||||
<record id="ir_ui_view_sc_partner0" model="ir.ui.view_sc">
|
||||
<field name="name">Customers</field>
|
||||
<field name="resource">ir.ui.menu</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="res_id" ref="menu_partner_form"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -81,9 +81,9 @@ class res_company(osv.osv):
|
||||
'rml_header1': fields.char('Report Header', size=200),
|
||||
'rml_footer1': fields.char('Report Footer 1', size=200),
|
||||
'rml_footer2': fields.char('Report Footer 2', size=200),
|
||||
'rml_header' : fields.text('RML Header'),
|
||||
'rml_header2' : fields.text('RML Internal Header'),
|
||||
'rml_header3' : fields.text('RML Internal Header'),
|
||||
'rml_header' : fields.text('RML Header', required=True),
|
||||
'rml_header2' : fields.text('RML Internal Header', required=True),
|
||||
'rml_header3' : fields.text('RML Internal Header', required=True),
|
||||
'logo' : fields.binary('Logo'),
|
||||
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
|
||||
'currency_ids': fields.one2many('res.currency', 'company_id', 'Currency'),
|
||||
|
||||
@@ -19,18 +19,22 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import locale
|
||||
import logging
|
||||
|
||||
from osv import fields, osv
|
||||
from locale import localeconv
|
||||
import tools
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
from tools.translate import _
|
||||
import locale
|
||||
import logging
|
||||
|
||||
class lang(osv.osv):
|
||||
_name = "res.lang"
|
||||
_description = "Languages"
|
||||
|
||||
_disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys()
|
||||
_disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice
|
||||
|
||||
def install_lang(self, cr, uid, **args):
|
||||
lang = tools.config.get('lang')
|
||||
if not lang:
|
||||
@@ -66,17 +70,30 @@ class lang(osv.osv):
|
||||
|
||||
|
||||
def fix_xa0(s):
|
||||
"""Fix badly-encoded non-breaking space Unicode character from locale.localeconv(),
|
||||
coercing to utf-8, as some platform seem to output localeconv() in their system
|
||||
encoding, e.g. Windows-1252"""
|
||||
if s == '\xa0':
|
||||
return '\xc2\xa0'
|
||||
return s
|
||||
|
||||
def fix_datetime_format(format):
|
||||
"""Python's strftime supports only the format directives
|
||||
that are available on the platform's libc, so in order to
|
||||
be 100% cross-platform we map to the directives required by
|
||||
the C standard (1989 version), always available on platforms
|
||||
with a C standard implementation."""
|
||||
for pattern, replacement in tools.DATETIME_FORMATS_MAP.iteritems():
|
||||
format = format.replace(pattern, replacement)
|
||||
return str(format)
|
||||
|
||||
lang_info = {
|
||||
'code': lang,
|
||||
'iso_code': iso_lang,
|
||||
'name': lang_name,
|
||||
'translatable': 1,
|
||||
'date_format' : str(locale.nl_langinfo(locale.D_FMT).replace('%y', '%Y')),
|
||||
'time_format' : str(locale.nl_langinfo(locale.T_FMT)),
|
||||
'date_format' : fix_datetime_format(locale.nl_langinfo(locale.D_FMT)),
|
||||
'time_format' : fix_datetime_format(locale.nl_langinfo(locale.T_FMT)),
|
||||
'decimal_point' : fix_xa0(str(locale.localeconv()['decimal_point'])),
|
||||
'thousands_sep' : fix_xa0(str(locale.localeconv()['thousands_sep'])),
|
||||
}
|
||||
@@ -87,6 +104,14 @@ class lang(osv.osv):
|
||||
tools.resetlocale()
|
||||
return lang_id
|
||||
|
||||
def _check_format(self, cr, uid, ids, context=None):
|
||||
for lang in self.browse(cr, uid, ids, context=context):
|
||||
for pattern in self._disallowed_datetime_patterns:
|
||||
if (lang.time_format and pattern in lang.time_format)\
|
||||
or (lang.date_format and pattern in lang.date_format):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_default_date_format(self,cursor,user,context={}):
|
||||
return '%m/%d/%Y'
|
||||
|
||||
@@ -121,6 +146,10 @@ class lang(osv.osv):
|
||||
('code_uniq', 'unique (code)', 'The code of the language must be unique !'),
|
||||
]
|
||||
|
||||
_constraints = [
|
||||
(_check_format, 'Invalid date/time format directive specified. Please refer to the list of allowed directives, displayed when you edit a language.', ['time_format', 'date_format'])
|
||||
]
|
||||
|
||||
@tools.cache(skiparg=3)
|
||||
def _lang_data_get(self, cr, uid, lang_id, monetary=False):
|
||||
conv = localeconv()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="model">res.lang</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom" string="Languages">
|
||||
<tree string="Languages">
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="iso_code"/>
|
||||
|
||||
@@ -54,9 +54,9 @@ class res_request(osv.osv):
|
||||
return True
|
||||
|
||||
def request_get(self, cr, uid):
|
||||
cr.execute('select id from res_request where act_to=%s and (trigger_date<=%s or trigger_date is null) and active=True', (uid,time.strftime('%Y-%m-%d')))
|
||||
cr.execute('select id from res_request where act_to=%s and (trigger_date<=%s or trigger_date is null) and active=True and state != %s', (uid,time.strftime('%Y-%m-%d'), 'closed'))
|
||||
ids = map(lambda x:x[0], cr.fetchall())
|
||||
cr.execute('select id from res_request where act_from=%s and (act_to<>%s) and (trigger_date<=%s or trigger_date is null) and active=True', (uid,uid,time.strftime('%Y-%m-%d')))
|
||||
cr.execute('select id from res_request where act_from=%s and (act_to<>%s) and (trigger_date<=%s or trigger_date is null) and active=True and state != %s', (uid,uid,time.strftime('%Y-%m-%d'), 'closed'))
|
||||
ids2 = map(lambda x:x[0], cr.fetchall())
|
||||
return (ids, ids2)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
# Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
@@ -28,7 +29,6 @@ import pooler
|
||||
from tools.translate import _
|
||||
from service import security
|
||||
import netsvc
|
||||
import time
|
||||
|
||||
class groups(osv.osv):
|
||||
_name = "res.groups"
|
||||
@@ -411,11 +411,12 @@ class users(osv.osv):
|
||||
if default is None:
|
||||
default = {}
|
||||
copy_pattern = _("%s (copy)")
|
||||
default.update(login=(copy_pattern % user2copy['login']),
|
||||
copydef = dict(login=(copy_pattern % user2copy['login']),
|
||||
name=(copy_pattern % user2copy['name']),
|
||||
address_id=False, # avoid sharing the address of the copied user!
|
||||
)
|
||||
return super(users, self).copy(cr, uid, id, default, context)
|
||||
copydef.update(default)
|
||||
return super(users, self).copy(cr, uid, id, copydef, context)
|
||||
|
||||
def context_get(self, cr, uid, context=None):
|
||||
user = self.browse(cr, uid, uid, context)
|
||||
@@ -459,8 +460,7 @@ class users(osv.osv):
|
||||
def check(self, db, uid, passwd):
|
||||
if not passwd:
|
||||
return False
|
||||
cached_pass = self._uid_cache.get(db, {}).get(uid)
|
||||
if (cached_pass is not None) and cached_pass == passwd:
|
||||
if self._uid_cache.get(db, {}).get(uid) == passwd:
|
||||
return True
|
||||
cr = pooler.get_db(db).cursor()
|
||||
try:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="openerp_favorites_twitter_widget" model="res.widget">
|
||||
<field name="title">OpenERP Favorites</field>
|
||||
<field name="title">OpenERP Tweets</field>
|
||||
<field name="content"><![CDATA[
|
||||
<script type="text/javascript" src="http://widgets.twimg.com/j/2/widget.js"></script>
|
||||
<script type="text/javascript">
|
||||
@@ -42,31 +42,31 @@ new TWTR.Widget({
|
||||
<record id="events_widget" model="res.widget">
|
||||
<field name="title">Events</field>
|
||||
<field name="content"><![CDATA[<script src="http://widgets.twimg.com/j/2/widget.js"></script>
|
||||
<div style="width:195px; text-align:center;" ><iframe src="http://www.eventbrite.com/calendar-widget?eid=852573069" frameborder="0" height="406" width="220" marginheight="0" marginwidth="0" scrolling="no" ></iframe><div style="font-family:Helvetica, Arial; font-size:10px; padding:5px 0 5px; margin:2px; width:195px; text-align:center;" ><a style="color:#ddd; text-decoration:none;" target="_blank" href="http://www.eventbrite.com/features?ref=ecal" >Online Ticketing</a><span style="color:#ddd;" > powered by </span><a style="color:#ddd; text-decoration:none;" target="_blank" href="http://www.eventbrite.com?ref=ecal" >Eventbrite</a></div>]]></field>
|
||||
<div style="width:195px; text-align:center;" ><iframe src="http://www.eventbrite.com/calendar-widget?eid=852573069" frameborder="0" height="626" width="220" marginheight="0" marginwidth="0" scrolling="no" ></iframe><div style="font-family:Helvetica, Arial; font-size:10px; padding:5px 0 5px; margin:2px; width:195px; text-align:center;" ><a style="color:#ddd; text-decoration:none;" target="_blank" href="http://www.eventbrite.com/features?ref=ecal" >Online Ticketing</a><span style="color:#ddd;" > powered by </span><a style="color:#ddd; text-decoration:none;" target="_blank" href="http://www.eventbrite.com?ref=ecal" >Eventbrite</a></div>]]></field>
|
||||
</record>
|
||||
|
||||
<record id="facebook_widget" model="res.widget">
|
||||
<field name="title">Facebook</field>
|
||||
<field name="content"><![CDATA[<script src="http://widgets.twimg.com/j/2/widget.js"></script>
|
||||
<iframe src="http://www.facebook.com/plugins/activity.php?site=www.facebook.com%2FOpenERP&width=300&height=300&header=true&colorscheme=light&recommendations=true" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:300px; height:300px;" allowTransparency="true"></iframe>]]></field>
|
||||
<iframe src="http://www.facebook.com/plugins/activity.php?site=www.facebook.com/OpenERP&width=399&height=300&header=true&colorscheme=light&recommendations=true" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:399px; height:300px;" allowTransparency="true"></iframe>]]></field>
|
||||
</record>
|
||||
|
||||
<record id="note_widget" model="res.widget">
|
||||
<field name="title">Notes</field>
|
||||
<field name="content"><![CDATA[<script src="http://widgets.twimg.com/j/2/widget.js"></script>
|
||||
<script src="http://www.gmodules.com/ig/ifr?url=http://www.google.com/ig/modules/sticky.xml&up_title=Note&up_bgcolor=white&up_textcolor=Black&up_stickydata=&synd=open&w=320&h=200&title=Notes&lang=all&country=ALL&border=%23ffffff%7C3px%2C1px+solid+%23999999&output=js"></script>]]></field>
|
||||
<script src="http://www.gmodules.com/ig/ifr?url=http://www.google.com/ig/modules/sticky.xml&up_title=Note&up_bgcolor=white&up_textcolor=Black&up_stickydata=&synd=open&w=399&h=200&title=Notes&lang=all&country=ALL&border=%23ffffff%7C3px%2C1px+solid+%23999999&output=js"></script>]]></field>
|
||||
</record>
|
||||
|
||||
<record id="google_maps_widget" model="res.widget">
|
||||
<field name="title">Google Maps</field>
|
||||
<field name="content"><![CDATA[<script src="http://widgets.twimg.com/j/2/widget.js"></script>
|
||||
<script src="http://www.gmodules.com/ig/ifr?url=http://www.google.com/uds/modules/elements/localsearch/localsearch.xml&up_location=Ramillies&up_largeMapMode=1&up_kml=1&up_traffic=&up_locationCacheString=&up_locationCacheLat=&up_locationCacheLng=&up_mapType=m&up_idleZoom=11&up_transitionQuery=&up_rawquery=&up_selectedtext=&synd=open&w=320&h=350&title=Map&lang=all&country=ALL&border=%23ffffff%7C3px%2C1px+solid+%23999999&output=js"></script>]]></field>
|
||||
<script src="http://www.gmodules.com/ig/ifr?url=http://www.google.com/uds/modules/elements/localsearch/localsearch.xml&up_location=Ramillies&up_largeMapMode=1&up_kml=1&up_traffic=&up_locationCacheString=&up_locationCacheLat=&up_locationCacheLng=&up_mapType=m&up_idleZoom=11&up_transitionQuery=&up_rawquery=&up_selectedtext=&synd=open&w=399&h=350&title=Map&lang=all&country=ALL&border=%23ffffff%7C3px%2C1px+solid+%23999999&output=js"></script>]]></field>
|
||||
</record>
|
||||
|
||||
<record id="currency_converter_widget" model="res.widget">
|
||||
<field name="title">Currency Converter</field>
|
||||
<field name="content"><![CDATA[<script src="http://widgets.twimg.com/j/2/widget.js"></script>
|
||||
<script src="http://www.gmodules.com/ig/ifr?url=http://www.pixelmedia.nl/gmodules/ucc.xml&up_fromcur=USD&up_tocur=EUR&synd=open&w=320&h=110&title=Currency+Converter&lang=all&country=ALL&border=%23ffffff%7C3px%2C1px+solid+%23999999&output=js"></script>]]></field>
|
||||
<script src="http://www.gmodules.com/ig/ifr?url=http://www.pixelmedia.nl/gmodules/ucc.xml&up_fromcur=USD&up_tocur=EUR&synd=open&w=399&h=110&title=Currency+Converter&lang=all&country=ALL&border=%23ffffff%7C3px%2C1px+solid+%23999999&output=js"></script>]]></field>
|
||||
</record>
|
||||
|
||||
<record id="openerp_favorites" model="res.widget.user">
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="events_admin_widgets" model="res.widget.user">
|
||||
<field name="widget_id" ref="events_widget"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
</record>
|
||||
<record id="events_demo_widgets" model="res.widget.user">
|
||||
<field name="widget_id" ref="events_widget"/>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"access_res_partner_bank_type_group_partner_manager","res_partner_bank_type group_partner_manager","model_res_partner_bank_type","group_partner_manager",1,1,1,1
|
||||
"access_res_partner_bank_type_group_user","res_partner_bank_type group_user","model_res_partner_bank_type","group_user",1,0,0,0
|
||||
"access_res_partner_bank_type_field_group_partner_manager","res_partner_bank_type_field group_partner_manager","model_res_partner_bank_type_field","group_partner_manager",1,1,1,1
|
||||
"access_res_partner_bank_type_field_group_user","res_partner_bank_type_field group_user","model_res_partner_bank_type_field","group_user",1,0,0,0
|
||||
"access_res_partner_bank_type_field_group_user","res_partner_bank_type_field group_user","model_res_partner_bank_type_field",,1,0,0,0
|
||||
"access_res_partner_canal_group_user","res_partner_canal group_user","model_res_partner_canal","group_user",1,0,0,0
|
||||
"access_res_partner_canal_group_partner_manager","res_partner_canal group_partner_manager","model_res_partner_canal","group_partner_manager",1,1,1,1
|
||||
"access_res_partner_category_group_user","res_partner_category group_user","model_res_partner_category","group_user",1,0,0,0
|
||||
|
||||
|
+4
-4
@@ -309,7 +309,7 @@ class Agent(object):
|
||||
"""
|
||||
__tasks = []
|
||||
__tasks_by_db = {}
|
||||
_logger = Logger()
|
||||
_logger = logging.getLogger('netsvc.agent')
|
||||
|
||||
@classmethod
|
||||
def setAlarm(cls, function, timestamp, db_name, *args, **kwargs):
|
||||
@@ -320,6 +320,7 @@ class Agent(object):
|
||||
@classmethod
|
||||
def cancel(cls, db_name):
|
||||
"""Cancel all tasks for a given database. If None is passed, all tasks are cancelled"""
|
||||
cls._logger.debug("Cancel timers for %s db", db_name or 'all')
|
||||
if db_name is None:
|
||||
cls.__tasks, cls.__tasks_by_db = [], {}
|
||||
else:
|
||||
@@ -334,7 +335,7 @@ class Agent(object):
|
||||
@classmethod
|
||||
def runner(cls):
|
||||
"""Neverending function (intended to be ran in a dedicated thread) that
|
||||
checks every 60 seconds tasks to run.
|
||||
checks every 60 seconds tasks to run. TODO: make configurable
|
||||
"""
|
||||
current_thread = threading.currentThread()
|
||||
while True:
|
||||
@@ -346,7 +347,7 @@ class Agent(object):
|
||||
# null timestamp -> cancelled task
|
||||
continue
|
||||
current_thread.dbname = dbname # hack hack
|
||||
cls._logger.notifyChannel('timers', LOG_DEBUG, "Run %s.%s(*%r, **%r)" % (function.im_class.__name__, function.func_name, args, kwargs))
|
||||
cls._logger.debug("Run %s.%s(*%s, **%s)", function.im_class.__name__, function.func_name, args, kwargs)
|
||||
delattr(current_thread, 'dbname')
|
||||
task_thread = threading.Thread(target=function, name='netsvc.Agent.task', args=args, kwargs=kwargs)
|
||||
# force non-daemon task threads (the runner thread must be daemon, and this property is inherited by default)
|
||||
@@ -362,7 +363,6 @@ agent_runner = threading.Thread(target=Agent.runner, name="netsvc.Agent.runner")
|
||||
agent_runner.setDaemon(True)
|
||||
agent_runner.start()
|
||||
|
||||
|
||||
import traceback
|
||||
|
||||
class Server:
|
||||
|
||||
@@ -167,12 +167,15 @@ class expression(object):
|
||||
self.__exp[i] = self.__DUMMY_LEAF
|
||||
else:
|
||||
subexp = field.search(cr, uid, table, left, [self.__exp[i]], context=context)
|
||||
# we assume that the expression is valid
|
||||
# we create a dummy leaf for forcing the parsing of the resulting expression
|
||||
self.__exp[i] = '&'
|
||||
self.__exp.insert(i + 1, self.__DUMMY_LEAF)
|
||||
for j, se in enumerate(subexp):
|
||||
self.__exp.insert(i + 2 + j, se)
|
||||
if not subexp:
|
||||
self.__exp[i] = self.__DUMMY_LEAF
|
||||
else:
|
||||
# we assume that the expression is valid
|
||||
# we create a dummy leaf for forcing the parsing of the resulting expression
|
||||
self.__exp[i] = '&'
|
||||
self.__exp.insert(i + 1, self.__DUMMY_LEAF)
|
||||
for j, se in enumerate(subexp):
|
||||
self.__exp.insert(i + 2 + j, se)
|
||||
# else, the value of the field is store in the database, so we search on it
|
||||
|
||||
elif field._type == 'one2many':
|
||||
|
||||
+23
-12
@@ -123,13 +123,6 @@ class boolean(_column):
|
||||
_symbol_f = lambda x: x and 'True' or 'False'
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
|
||||
class integer_big(_column):
|
||||
_type = 'integer_big'
|
||||
_symbol_c = '%s'
|
||||
_symbol_f = lambda x: int(x or 0)
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
_symbol_get = lambda self,x: x or 0
|
||||
|
||||
class integer(_column):
|
||||
_type = 'integer'
|
||||
_symbol_c = '%s'
|
||||
@@ -137,6 +130,14 @@ class integer(_column):
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
_symbol_get = lambda self,x: x or 0
|
||||
|
||||
class integer_big(_column):
|
||||
_type = 'integer_big'
|
||||
# do not reference the _symbol_* of integer class, as that would possibly
|
||||
# unbind the lambda functions
|
||||
_symbol_c = '%s'
|
||||
_symbol_f = lambda x: int(x or 0)
|
||||
_symbol_set = (_symbol_c, _symbol_f)
|
||||
_symbol_get = lambda self,x: x or 0
|
||||
|
||||
class reference(_column):
|
||||
_type = 'reference'
|
||||
@@ -356,7 +357,9 @@ class many2one(_column):
|
||||
# build a dictionary of the form {'id_of_distant_resource': name_of_distant_resource}
|
||||
# we use uid=1 because the visibility of a many2one field value (just id and name)
|
||||
# must be the access right of the parent form and not the linked object itself.
|
||||
records = dict(obj.name_get(cr, 1, list(set(filter(None, res.values()))), context=context))
|
||||
records = dict(obj.name_get(cr, 1,
|
||||
list(set([x for x in res.values() if isinstance(x, (int,long))])),
|
||||
context=context))
|
||||
for id in res:
|
||||
if res[id] in records:
|
||||
res[id] = (res[id], records[res[id]])
|
||||
@@ -762,6 +765,11 @@ class function(_column):
|
||||
self._symbol_f = boolean._symbol_f
|
||||
self._symbol_set = boolean._symbol_set
|
||||
|
||||
if type in ['integer','integer_big']:
|
||||
self._symbol_c = integer._symbol_c
|
||||
self._symbol_f = integer._symbol_f
|
||||
self._symbol_set = integer._symbol_set
|
||||
|
||||
def digits_change(self, cr):
|
||||
if self.digits_compute:
|
||||
t = self.digits_compute(cr)
|
||||
@@ -1049,11 +1057,14 @@ class property(function):
|
||||
brs = properties.browse(cr, uid, nids, context=context)
|
||||
for prop in brs:
|
||||
value = properties.get_by_record(cr, uid, prop, context=context)
|
||||
record_exists = obj.pool.get(value._name).exists(cr, uid, value.id)
|
||||
res[prop.res_id.id][prop.fields_id.name] = (record_exists and value) and value or False
|
||||
res[prop.res_id.id][prop.fields_id.name] = value or False
|
||||
if value and (prop.type == 'many2one'):
|
||||
replaces.setdefault(value._name, {})
|
||||
replaces[value._name][value.id] = True
|
||||
record_exists = obj.pool.get(value._name).exists(cr, uid, value.id)
|
||||
if record_exists:
|
||||
replaces.setdefault(value._name, {})
|
||||
replaces[value._name][value.id] = True
|
||||
else:
|
||||
res[prop.res_id.id][prop.fields_id.name] = False
|
||||
|
||||
for rep in replaces:
|
||||
nids = obj.pool.get(rep).search(cr, uid, [('id','in',replaces[rep].keys())], context=context)
|
||||
|
||||
+35
-20
@@ -203,6 +203,8 @@ class browse_record(object):
|
||||
# read the results
|
||||
field_names = map(lambda x: x[0], fields_to_fetch)
|
||||
field_values = self._table.read(self._cr, self._uid, ids, field_names, context=self._context, load="_classic_write")
|
||||
|
||||
# TODO: improve this, very slow for reports
|
||||
if self._fields_process:
|
||||
lang = self._context.get('lang', 'en_US') or 'en_US'
|
||||
lang_obj_ids = self.pool.get('res.lang').search(self._cr, self._uid, [('code', '=', lang)])
|
||||
@@ -423,7 +425,7 @@ class orm_template(object):
|
||||
model_id = cr.fetchone()[0]
|
||||
if 'module' in context:
|
||||
name_id = 'model_'+self._name.replace('.', '_')
|
||||
cr.execute('select * from ir_model_data where name=%s and res_id=%s and module=%s', (name_id, model_id, context['module']))
|
||||
cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, context['module']))
|
||||
if not cr.rowcount:
|
||||
cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module,model,res_id) VALUES (%s, now(), now(), %s, %s, %s)", \
|
||||
(name_id, context['module'], 'ir.model', model_id)
|
||||
@@ -726,7 +728,7 @@ class orm_template(object):
|
||||
id = ir_model_data[0]['res_id']
|
||||
else:
|
||||
obj_model = self.pool.get(model_name)
|
||||
ids = obj_model.name_search(cr, uid, id)
|
||||
ids = obj_model.name_search(cr, uid, id, operator='=')
|
||||
if not ids:
|
||||
raise ValueError('No record found for %s' % (id,))
|
||||
id = ids[0][0]
|
||||
@@ -2134,10 +2136,11 @@ class orm(orm_template):
|
||||
|
||||
# Take care of adding join(s) if groupby is an '_inherits'ed field
|
||||
groupby_list = groupby
|
||||
qualified_groupby_field = groupby
|
||||
if groupby:
|
||||
if isinstance(groupby, list):
|
||||
groupby = groupby[0]
|
||||
self._inherits_join_calc(groupby, query)
|
||||
qualified_groupby_field = self._inherits_join_calc(groupby, query)
|
||||
|
||||
if groupby:
|
||||
assert not groupby or groupby in fields, "Fields in 'groupby' must appear in the list of fields to read (perhaps it's missing in the list view?)"
|
||||
@@ -2151,10 +2154,10 @@ class orm(orm_template):
|
||||
if groupby:
|
||||
if fget.get(groupby):
|
||||
if fget[groupby]['type'] in ('date', 'datetime'):
|
||||
flist = "to_char(%s,'yyyy-mm') as %s " % (groupby, groupby)
|
||||
groupby = "to_char(%s,'yyyy-mm')" % (groupby)
|
||||
flist = "to_char(%s,'yyyy-mm') as %s " % (qualified_groupby_field, groupby)
|
||||
groupby = "to_char(%s,'yyyy-mm')" % (qualified_groupby_field)
|
||||
else:
|
||||
flist = groupby
|
||||
flist = qualified_groupby_field
|
||||
else:
|
||||
# Don't allow arbitrary values, as this would be a SQL injection vector!
|
||||
raise except_orm(_('Invalid group_by'),
|
||||
@@ -2168,10 +2171,11 @@ class orm(orm_template):
|
||||
if f not in ['id', 'sequence']:
|
||||
group_operator = fget[f].get('group_operator', 'sum')
|
||||
if flist:
|
||||
flist += ','
|
||||
flist += group_operator+'('+f+') as '+f
|
||||
flist += ', '
|
||||
qualified_field = '"%s"."%s"' % (self._table, f)
|
||||
flist += "%s(%s) AS %s" % (group_operator, qualified_field, f)
|
||||
|
||||
gb = groupby and (' GROUP BY '+groupby) or ''
|
||||
gb = groupby and (' GROUP BY ' + qualified_groupby_field) or ''
|
||||
|
||||
from_clause, where_clause, where_clause_params = query.get_sql()
|
||||
where_clause = where_clause and ' WHERE ' + where_clause
|
||||
@@ -2559,7 +2563,7 @@ class orm(orm_template):
|
||||
# add the NOT NULL constraint
|
||||
cr.commit()
|
||||
try:
|
||||
cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
|
||||
cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
|
||||
cr.commit()
|
||||
self.__schema.debug("Table '%s': column '%s': added NOT NULL constraint",
|
||||
self._table, k)
|
||||
@@ -2592,7 +2596,7 @@ class orm(orm_template):
|
||||
cr.execute('DROP INDEX "%s_%s_index"' % (self._table, k))
|
||||
cr.commit()
|
||||
msg = "Table '%s': dropping index for column '%s' of type '%s' as it is not required anymore"
|
||||
self.__schema.warn(msg, self._table, k, f._type)
|
||||
self.__schema.debug(msg, self._table, k, f._type)
|
||||
|
||||
if isinstance(f, fields.many2one):
|
||||
ref = self.pool.get(f._obj)._table
|
||||
@@ -2665,7 +2669,7 @@ class orm(orm_template):
|
||||
if f.required:
|
||||
try:
|
||||
cr.commit()
|
||||
cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k))
|
||||
cr.execute('ALTER TABLE "%s" ALTER COLUMN "%s" SET NOT NULL' % (self._table, k), log_exceptions=False)
|
||||
self.__schema.debug("Table '%s': column '%s': added a NOT NULL constraint",
|
||||
self._table, k)
|
||||
except Exception:
|
||||
@@ -2908,7 +2912,7 @@ class orm(orm_template):
|
||||
context = {}
|
||||
self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context)
|
||||
if not fields:
|
||||
fields = self._columns.keys() + self._inherit_fields.keys()
|
||||
fields = list(set(self._columns.keys() + self._inherit_fields.keys()))
|
||||
if isinstance(ids, (int, long)):
|
||||
select = [ids]
|
||||
else:
|
||||
@@ -3460,16 +3464,16 @@ class orm(orm_template):
|
||||
result.sort()
|
||||
|
||||
done = {}
|
||||
for order, object, ids, fields in result:
|
||||
key = (object, tuple(fields))
|
||||
for order, object, ids_to_update, fields_to_recompute in result:
|
||||
key = (object, tuple(fields_to_recompute))
|
||||
done.setdefault(key, {})
|
||||
# avoid to do several times the same computation
|
||||
todo = []
|
||||
for id in ids:
|
||||
for id in ids_to_update:
|
||||
if id not in done[key]:
|
||||
done[key][id] = True
|
||||
todo.append(id)
|
||||
self.pool.get(object)._store_set_values(cr, user, todo, fields, context)
|
||||
self.pool.get(object)._store_set_values(cr, user, todo, fields_to_recompute, context)
|
||||
|
||||
wf_service = netsvc.LocalService("workflow")
|
||||
for id in ids:
|
||||
@@ -3657,6 +3661,15 @@ class orm(orm_template):
|
||||
return id_new
|
||||
|
||||
def _store_get_values(self, cr, uid, ids, fields, context):
|
||||
"""Returns an ordered list of fields.functions to call due to
|
||||
an update operation on ``fields`` of records with ``ids``,
|
||||
obtained by calling the 'store' functions of these fields,
|
||||
as setup by their 'store' attribute.
|
||||
|
||||
:return: [(priority, model_name, [record_ids,], [function_fields,])]
|
||||
"""
|
||||
# FIXME: rewrite, cleanup, use real variable names
|
||||
# e.g.: http://pastie.org/1222060
|
||||
result = {}
|
||||
fncts = self.pool._store_function.get(self._name, [])
|
||||
for fnct in range(len(fncts)):
|
||||
@@ -3695,6 +3708,8 @@ class orm(orm_template):
|
||||
return result2
|
||||
|
||||
def _store_set_values(self, cr, uid, ids, fields, context):
|
||||
"""Calls the fields.function's "implementation function" for all ``fields``, on records with ``ids`` (taking care of
|
||||
respecting ``multi`` attributes), and stores the resulting values in the database directly."""
|
||||
if not ids:
|
||||
return True
|
||||
field_flag = False
|
||||
@@ -3878,7 +3893,7 @@ class orm(orm_template):
|
||||
else:
|
||||
# extract the field names, to be able to qualify them and add desc/asc
|
||||
m2o_order_list = []
|
||||
for order_part in m2o_order.split(",",1):
|
||||
for order_part in m2o_order.split(","):
|
||||
m2o_order_list.append(order_part.strip().split(" ",1)[0].strip())
|
||||
m2o_order = m2o_order_list
|
||||
|
||||
@@ -4175,7 +4190,7 @@ class orm(orm_template):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_xml_ids(self, cr, uid, ids, *args, **kwargs):
|
||||
def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
|
||||
"""Find out the XML ID(s) of any database record.
|
||||
|
||||
**Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
|
||||
@@ -4209,7 +4224,7 @@ class orm(orm_template):
|
||||
defaulting to an empty string when there's none
|
||||
(to be usable as a function field).
|
||||
"""
|
||||
results = self.get_xml_ids(cr, uid, ids)
|
||||
results = self._get_xml_ids(cr, uid, ids)
|
||||
for k, v in results.items():
|
||||
if results[k]:
|
||||
results[k] = v[0]
|
||||
|
||||
+6
-1
@@ -340,7 +340,12 @@ class osv(osv_base, orm.orm):
|
||||
exist = False
|
||||
for c2 in range(len(new)):
|
||||
#For _constraints, we should check field and methods as well
|
||||
if new[c2][2]==c[2] and new[c2][0]==c[0]:
|
||||
if new[c2][2]==c[2] and (new[c2][0] == c[0] \
|
||||
or getattr(new[c2][0],'__name__', True) == \
|
||||
getattr(c[0],'__name__', False)):
|
||||
# If new class defines a constraint with
|
||||
# same function name, we let it override
|
||||
# the old one.
|
||||
new[c2] = c
|
||||
exist = True
|
||||
break
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
##############################################################################
|
||||
|
||||
name = 'openerp-server'
|
||||
version = '6.0.0-rc2'
|
||||
version = '6.0.0'
|
||||
major_version = '6.0'
|
||||
description = 'OpenERP Server'
|
||||
long_desc = '''OpenERP is a complete ERP and CRM. The main features are accounting (analytic
|
||||
|
||||
@@ -33,16 +33,12 @@ import render
|
||||
import urllib
|
||||
|
||||
#
|
||||
# encode a value to a string in utf8 and converts XML entities
|
||||
# coerce any type to a unicode string (to preserve non-ascii characters)
|
||||
# and escape XML entities
|
||||
#
|
||||
def toxml(val):
|
||||
if isinstance(val, str):
|
||||
str_utf8 = val
|
||||
elif isinstance(val, unicode):
|
||||
str_utf8 = tools.ustr(val)
|
||||
else:
|
||||
str_utf8 = str(val)
|
||||
return str_utf8.replace('&', '&').replace('<','<').replace('>','>')
|
||||
def toxml(value):
|
||||
unicode_value = tools.ustr(value)
|
||||
return unicode_value.replace('&', '&').replace('<','<').replace('>','>')
|
||||
|
||||
class report_int(netsvc.Service):
|
||||
def __init__(self, name, audience='*'):
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from pdf import PdfFileReader, PdfFileWriter
|
||||
__all__ = ["pdf"]
|
||||
|
||||
+252
-253
@@ -1,253 +1,252 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: sw=4:expandtab:foldmethod=marker
|
||||
#
|
||||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""
|
||||
Implementation of stream filters for PDF.
|
||||
"""
|
||||
__author__ = "Mathieu Fenniak"
|
||||
__author_email__ = "biziqe@mathieu.fenniak.net"
|
||||
|
||||
from utils import PdfReadError
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
import zlib
|
||||
def decompress(data):
|
||||
return zlib.decompress(data)
|
||||
def compress(data):
|
||||
return zlib.compress(data)
|
||||
except ImportError:
|
||||
# Unable to import zlib. Attempt to use the System.IO.Compression
|
||||
# library from the .NET framework. (IronPython only)
|
||||
import System
|
||||
from System import IO, Collections, Array
|
||||
def _string_to_bytearr(buf):
|
||||
retval = Array.CreateInstance(System.Byte, len(buf))
|
||||
for i in range(len(buf)):
|
||||
retval[i] = ord(buf[i])
|
||||
return retval
|
||||
def _bytearr_to_string(bytes):
|
||||
retval = ""
|
||||
for i in range(bytes.Length):
|
||||
retval += chr(bytes[i])
|
||||
return retval
|
||||
def _read_bytes(stream):
|
||||
ms = IO.MemoryStream()
|
||||
buf = Array.CreateInstance(System.Byte, 2048)
|
||||
while True:
|
||||
bytes = stream.Read(buf, 0, buf.Length)
|
||||
if bytes == 0:
|
||||
break
|
||||
else:
|
||||
ms.Write(buf, 0, bytes)
|
||||
retval = ms.ToArray()
|
||||
ms.Close()
|
||||
return retval
|
||||
def decompress(data):
|
||||
bytes = _string_to_bytearr(data)
|
||||
ms = IO.MemoryStream()
|
||||
ms.Write(bytes, 0, bytes.Length)
|
||||
ms.Position = 0 # fseek 0
|
||||
gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Decompress)
|
||||
bytes = _read_bytes(gz)
|
||||
retval = _bytearr_to_string(bytes)
|
||||
gz.Close()
|
||||
return retval
|
||||
def compress(data):
|
||||
bytes = _string_to_bytearr(data)
|
||||
ms = IO.MemoryStream()
|
||||
gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Compress, True)
|
||||
gz.Write(bytes, 0, bytes.Length)
|
||||
gz.Close()
|
||||
ms.Position = 0 # fseek 0
|
||||
bytes = ms.ToArray()
|
||||
retval = _bytearr_to_string(bytes)
|
||||
ms.Close()
|
||||
return retval
|
||||
|
||||
|
||||
class FlateDecode(object):
|
||||
def decode(data, decodeParms):
|
||||
data = decompress(data)
|
||||
predictor = 1
|
||||
if decodeParms:
|
||||
predictor = decodeParms.get("/Predictor", 1)
|
||||
# predictor 1 == no predictor
|
||||
if predictor != 1:
|
||||
columns = decodeParms["/Columns"]
|
||||
# PNG prediction:
|
||||
if predictor >= 10 and predictor <= 15:
|
||||
output = StringIO()
|
||||
# PNG prediction can vary from row to row
|
||||
rowlength = columns + 1
|
||||
assert len(data) % rowlength == 0
|
||||
prev_rowdata = (0,) * rowlength
|
||||
for row in xrange(len(data) / rowlength):
|
||||
rowdata = [ord(x) for x in data[(row*rowlength):((row+1)*rowlength)]]
|
||||
filterByte = rowdata[0]
|
||||
if filterByte == 0:
|
||||
pass
|
||||
elif filterByte == 1:
|
||||
for i in range(2, rowlength):
|
||||
rowdata[i] = (rowdata[i] + rowdata[i-1]) % 256
|
||||
elif filterByte == 2:
|
||||
for i in range(1, rowlength):
|
||||
rowdata[i] = (rowdata[i] + prev_rowdata[i]) % 256
|
||||
else:
|
||||
# unsupported PNG filter
|
||||
raise PdfReadError("Unsupported PNG filter %r" % filterByte)
|
||||
prev_rowdata = rowdata
|
||||
output.write(''.join([chr(x) for x in rowdata[1:]]))
|
||||
data = output.getvalue()
|
||||
else:
|
||||
# unsupported predictor
|
||||
raise PdfReadError("Unsupported flatedecode predictor %r" % predictor)
|
||||
return data
|
||||
decode = staticmethod(decode)
|
||||
|
||||
def encode(data):
|
||||
return compress(data)
|
||||
encode = staticmethod(encode)
|
||||
|
||||
class ASCIIHexDecode(object):
|
||||
def decode(data, decodeParms=None):
|
||||
retval = ""
|
||||
char = ""
|
||||
x = 0
|
||||
while True:
|
||||
c = data[x]
|
||||
if c == ">":
|
||||
break
|
||||
elif c.isspace():
|
||||
x += 1
|
||||
continue
|
||||
char += c
|
||||
if len(char) == 2:
|
||||
retval += chr(int(char, base=16))
|
||||
char = ""
|
||||
x += 1
|
||||
assert char == ""
|
||||
return retval
|
||||
decode = staticmethod(decode)
|
||||
|
||||
class ASCII85Decode(object):
|
||||
def decode(data, decodeParms=None):
|
||||
retval = ""
|
||||
group = []
|
||||
x = 0
|
||||
hitEod = False
|
||||
# remove all whitespace from data
|
||||
data = [y for y in data if not (y in ' \n\r\t')]
|
||||
while not hitEod:
|
||||
c = data[x]
|
||||
if len(retval) == 0 and c == "<" and data[x+1] == "~":
|
||||
x += 2
|
||||
continue
|
||||
#elif c.isspace():
|
||||
# x += 1
|
||||
# continue
|
||||
elif c == 'z':
|
||||
assert len(group) == 0
|
||||
retval += '\x00\x00\x00\x00'
|
||||
continue
|
||||
elif c == "~" and data[x+1] == ">":
|
||||
if len(group) != 0:
|
||||
# cannot have a final group of just 1 char
|
||||
assert len(group) > 1
|
||||
cnt = len(group) - 1
|
||||
group += [ 85, 85, 85 ]
|
||||
hitEod = cnt
|
||||
else:
|
||||
break
|
||||
else:
|
||||
c = ord(c) - 33
|
||||
assert c >= 0 and c < 85
|
||||
group += [ c ]
|
||||
if len(group) >= 5:
|
||||
b = group[0] * (85**4) + \
|
||||
group[1] * (85**3) + \
|
||||
group[2] * (85**2) + \
|
||||
group[3] * 85 + \
|
||||
group[4]
|
||||
assert b < (2**32 - 1)
|
||||
c4 = chr((b >> 0) % 256)
|
||||
c3 = chr((b >> 8) % 256)
|
||||
c2 = chr((b >> 16) % 256)
|
||||
c1 = chr(b >> 24)
|
||||
retval += (c1 + c2 + c3 + c4)
|
||||
if hitEod:
|
||||
retval = retval[:-4+hitEod]
|
||||
group = []
|
||||
x += 1
|
||||
return retval
|
||||
decode = staticmethod(decode)
|
||||
|
||||
def decodeStreamData(stream):
|
||||
from generic import NameObject
|
||||
filters = stream.get("/Filter", ())
|
||||
if len(filters) and not isinstance(filters[0], NameObject):
|
||||
# we have a single filter instance
|
||||
filters = (filters,)
|
||||
data = stream._data
|
||||
for filterType in filters:
|
||||
if filterType == "/FlateDecode":
|
||||
data = FlateDecode.decode(data, stream.get("/DecodeParms"))
|
||||
elif filterType == "/ASCIIHexDecode":
|
||||
data = ASCIIHexDecode.decode(data)
|
||||
elif filterType == "/ASCII85Decode":
|
||||
data = ASCII85Decode.decode(data)
|
||||
elif filterType == "/Crypt":
|
||||
decodeParams = stream.get("/DecodeParams", {})
|
||||
if "/Name" not in decodeParams and "/Type" not in decodeParams:
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet")
|
||||
else:
|
||||
# unsupported filter
|
||||
raise NotImplementedError("unsupported filter %s" % filterType)
|
||||
return data
|
||||
|
||||
if __name__ == "__main__":
|
||||
assert "abc" == ASCIIHexDecode.decode('61\n626\n3>')
|
||||
|
||||
ascii85Test = """
|
||||
<~9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKF<GL>Cj@.4Gp$d7F!,L7@<6@)/0JDEF<G%<+EV:2F!,
|
||||
O<DJ+*.@<*K0@<6L(Df-\\0Ec5e;DffZ(EZee.Bl.9pF"AGXBPCsi+DGm>@3BB/F*&OCAfu2/AKY
|
||||
i(DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF<G:8+EV:.+Cf>-FD5W8ARlolDIa
|
||||
l(DId<j@<?3r@:F%a+D58'ATD4$Bl@l3De:,-DJs`8ARoFb/0JMK@qB4^F!,R<AKZ&-DfTqBG%G
|
||||
>uD.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c~>
|
||||
"""
|
||||
ascii85_originalText="Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
|
||||
assert ASCII85Decode.decode(ascii85Test) == ascii85_originalText
|
||||
|
||||
# vim: sw=4:expandtab:foldmethod=marker
|
||||
#
|
||||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""
|
||||
Implementation of stream filters for PDF.
|
||||
"""
|
||||
__author__ = "Mathieu Fenniak"
|
||||
__author_email__ = "biziqe@mathieu.fenniak.net"
|
||||
|
||||
from utils import PdfReadError
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
try:
|
||||
import zlib
|
||||
def decompress(data):
|
||||
return zlib.decompress(data)
|
||||
def compress(data):
|
||||
return zlib.compress(data)
|
||||
except ImportError:
|
||||
# Unable to import zlib. Attempt to use the System.IO.Compression
|
||||
# library from the .NET framework. (IronPython only)
|
||||
import System
|
||||
from System import IO, Collections, Array
|
||||
def _string_to_bytearr(buf):
|
||||
retval = Array.CreateInstance(System.Byte, len(buf))
|
||||
for i in range(len(buf)):
|
||||
retval[i] = ord(buf[i])
|
||||
return retval
|
||||
def _bytearr_to_string(bytes):
|
||||
retval = ""
|
||||
for i in range(bytes.Length):
|
||||
retval += chr(bytes[i])
|
||||
return retval
|
||||
def _read_bytes(stream):
|
||||
ms = IO.MemoryStream()
|
||||
buf = Array.CreateInstance(System.Byte, 2048)
|
||||
while True:
|
||||
bytes = stream.Read(buf, 0, buf.Length)
|
||||
if bytes == 0:
|
||||
break
|
||||
else:
|
||||
ms.Write(buf, 0, bytes)
|
||||
retval = ms.ToArray()
|
||||
ms.Close()
|
||||
return retval
|
||||
def decompress(data):
|
||||
bytes = _string_to_bytearr(data)
|
||||
ms = IO.MemoryStream()
|
||||
ms.Write(bytes, 0, bytes.Length)
|
||||
ms.Position = 0 # fseek 0
|
||||
gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Decompress)
|
||||
bytes = _read_bytes(gz)
|
||||
retval = _bytearr_to_string(bytes)
|
||||
gz.Close()
|
||||
return retval
|
||||
def compress(data):
|
||||
bytes = _string_to_bytearr(data)
|
||||
ms = IO.MemoryStream()
|
||||
gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Compress, True)
|
||||
gz.Write(bytes, 0, bytes.Length)
|
||||
gz.Close()
|
||||
ms.Position = 0 # fseek 0
|
||||
bytes = ms.ToArray()
|
||||
retval = _bytearr_to_string(bytes)
|
||||
ms.Close()
|
||||
return retval
|
||||
|
||||
|
||||
class FlateDecode(object):
|
||||
def decode(data, decodeParms):
|
||||
data = decompress(data)
|
||||
predictor = 1
|
||||
if decodeParms:
|
||||
predictor = decodeParms.get("/Predictor", 1)
|
||||
# predictor 1 == no predictor
|
||||
if predictor != 1:
|
||||
columns = decodeParms["/Columns"]
|
||||
# PNG prediction:
|
||||
if predictor >= 10 and predictor <= 15:
|
||||
output = StringIO()
|
||||
# PNG prediction can vary from row to row
|
||||
rowlength = columns + 1
|
||||
assert len(data) % rowlength == 0
|
||||
prev_rowdata = (0,) * rowlength
|
||||
for row in xrange(len(data) / rowlength):
|
||||
rowdata = [ord(x) for x in data[(row*rowlength):((row+1)*rowlength)]]
|
||||
filterByte = rowdata[0]
|
||||
if filterByte == 0:
|
||||
pass
|
||||
elif filterByte == 1:
|
||||
for i in range(2, rowlength):
|
||||
rowdata[i] = (rowdata[i] + rowdata[i-1]) % 256
|
||||
elif filterByte == 2:
|
||||
for i in range(1, rowlength):
|
||||
rowdata[i] = (rowdata[i] + prev_rowdata[i]) % 256
|
||||
else:
|
||||
# unsupported PNG filter
|
||||
raise PdfReadError("Unsupported PNG filter %r" % filterByte)
|
||||
prev_rowdata = rowdata
|
||||
output.write(''.join([chr(x) for x in rowdata[1:]]))
|
||||
data = output.getvalue()
|
||||
else:
|
||||
# unsupported predictor
|
||||
raise PdfReadError("Unsupported flatedecode predictor %r" % predictor)
|
||||
return data
|
||||
decode = staticmethod(decode)
|
||||
|
||||
def encode(data):
|
||||
return compress(data)
|
||||
encode = staticmethod(encode)
|
||||
|
||||
class ASCIIHexDecode(object):
|
||||
def decode(data, decodeParms=None):
|
||||
retval = ""
|
||||
char = ""
|
||||
x = 0
|
||||
while True:
|
||||
c = data[x]
|
||||
if c == ">":
|
||||
break
|
||||
elif c.isspace():
|
||||
x += 1
|
||||
continue
|
||||
char += c
|
||||
if len(char) == 2:
|
||||
retval += chr(int(char, base=16))
|
||||
char = ""
|
||||
x += 1
|
||||
assert char == ""
|
||||
return retval
|
||||
decode = staticmethod(decode)
|
||||
|
||||
class ASCII85Decode(object):
|
||||
def decode(data, decodeParms=None):
|
||||
retval = ""
|
||||
group = []
|
||||
x = 0
|
||||
hitEod = False
|
||||
# remove all whitespace from data
|
||||
data = [y for y in data if not (y in ' \n\r\t')]
|
||||
while not hitEod:
|
||||
c = data[x]
|
||||
if len(retval) == 0 and c == "<" and data[x+1] == "~":
|
||||
x += 2
|
||||
continue
|
||||
#elif c.isspace():
|
||||
# x += 1
|
||||
# continue
|
||||
elif c == 'z':
|
||||
assert len(group) == 0
|
||||
retval += '\x00\x00\x00\x00'
|
||||
continue
|
||||
elif c == "~" and data[x+1] == ">":
|
||||
if len(group) != 0:
|
||||
# cannot have a final group of just 1 char
|
||||
assert len(group) > 1
|
||||
cnt = len(group) - 1
|
||||
group += [ 85, 85, 85 ]
|
||||
hitEod = cnt
|
||||
else:
|
||||
break
|
||||
else:
|
||||
c = ord(c) - 33
|
||||
assert c >= 0 and c < 85
|
||||
group += [ c ]
|
||||
if len(group) >= 5:
|
||||
b = group[0] * (85**4) + \
|
||||
group[1] * (85**3) + \
|
||||
group[2] * (85**2) + \
|
||||
group[3] * 85 + \
|
||||
group[4]
|
||||
assert b < (2**32 - 1)
|
||||
c4 = chr((b >> 0) % 256)
|
||||
c3 = chr((b >> 8) % 256)
|
||||
c2 = chr((b >> 16) % 256)
|
||||
c1 = chr(b >> 24)
|
||||
retval += (c1 + c2 + c3 + c4)
|
||||
if hitEod:
|
||||
retval = retval[:-4+hitEod]
|
||||
group = []
|
||||
x += 1
|
||||
return retval
|
||||
decode = staticmethod(decode)
|
||||
|
||||
def decodeStreamData(stream):
|
||||
from generic import NameObject
|
||||
filters = stream.get("/Filter", ())
|
||||
if len(filters) and not isinstance(filters[0], NameObject):
|
||||
# we have a single filter instance
|
||||
filters = (filters,)
|
||||
data = stream._data
|
||||
for filterType in filters:
|
||||
if filterType == "/FlateDecode":
|
||||
data = FlateDecode.decode(data, stream.get("/DecodeParms"))
|
||||
elif filterType == "/ASCIIHexDecode":
|
||||
data = ASCIIHexDecode.decode(data)
|
||||
elif filterType == "/ASCII85Decode":
|
||||
data = ASCII85Decode.decode(data)
|
||||
elif filterType == "/Crypt":
|
||||
decodeParams = stream.get("/DecodeParams", {})
|
||||
if "/Name" not in decodeParams and "/Type" not in decodeParams:
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet")
|
||||
else:
|
||||
# unsupported filter
|
||||
raise NotImplementedError("unsupported filter %s" % filterType)
|
||||
return data
|
||||
|
||||
if __name__ == "__main__":
|
||||
assert "abc" == ASCIIHexDecode.decode('61\n626\n3>')
|
||||
|
||||
ascii85Test = """
|
||||
<~9jqo^BlbD-BleB1DJ+*+F(f,q/0JhKF<GL>Cj@.4Gp$d7F!,L7@<6@)/0JDEF<G%<+EV:2F!,
|
||||
O<DJ+*.@<*K0@<6L(Df-\\0Ec5e;DffZ(EZee.Bl.9pF"AGXBPCsi+DGm>@3BB/F*&OCAfu2/AKY
|
||||
i(DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF<G:8+EV:.+Cf>-FD5W8ARlolDIa
|
||||
l(DId<j@<?3r@:F%a+D58'ATD4$Bl@l3De:,-DJs`8ARoFb/0JMK@qB4^F!,R<AKZ&-DfTqBG%G
|
||||
>uD.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c~>
|
||||
"""
|
||||
ascii85_originalText="Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure."
|
||||
assert ASCII85Decode.decode(ascii85Test) == ascii85_originalText
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: sw=4:expandtab:foldmethod=marker
|
||||
#
|
||||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
@@ -207,15 +206,18 @@ class FloatObject(decimal.Decimal, PdfObject):
|
||||
def __new__(cls, value="0", context=None):
|
||||
return decimal.Decimal.__new__(cls, str(value), context)
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
if self == self.to_integral():
|
||||
return str(self.quantize(decimal.Decimal(1)))
|
||||
else:
|
||||
# XXX: this adds useless extraneous zeros.
|
||||
return "%.5f" % self
|
||||
def writeToStream(self, stream, encryption_key):
|
||||
stream.write(str(self))
|
||||
stream.write(repr(self))
|
||||
|
||||
|
||||
class NumberObject(int, PdfObject):
|
||||
def __init__(self, value):
|
||||
int.__init__(self)
|
||||
self = value
|
||||
int.__init__(value)
|
||||
|
||||
def writeToStream(self, stream, encryption_key):
|
||||
stream.write(repr(self))
|
||||
@@ -301,7 +303,7 @@ def readStringFromStream(stream):
|
||||
elif tok == "t":
|
||||
tok = "\t"
|
||||
elif tok == "b":
|
||||
tok == "\b"
|
||||
tok = "\b"
|
||||
elif tok == "f":
|
||||
tok = "\f"
|
||||
elif tok == "(":
|
||||
@@ -311,7 +313,17 @@ def readStringFromStream(stream):
|
||||
elif tok == "\\":
|
||||
tok = "\\"
|
||||
elif tok.isdigit():
|
||||
tok += stream.read(2)
|
||||
# "The number ddd may consist of one, two, or three
|
||||
# octal digits; high-order overflow shall be ignored.
|
||||
# Three octal digits shall be used, with leading zeros
|
||||
# as needed, if the next character of the string is also
|
||||
# a digit." (PDF reference 7.3.4.2, p 16)
|
||||
for i in range(2):
|
||||
ntok = stream.read(1)
|
||||
if ntok.isdigit():
|
||||
tok += ntok
|
||||
else:
|
||||
break
|
||||
tok = chr(int(tok, base=8))
|
||||
elif tok in "\n\r":
|
||||
# This case is hit when a backslash followed by a line
|
||||
@@ -405,8 +417,7 @@ class NameObject(str, PdfObject):
|
||||
delimiterCharacters = "(", ")", "<", ">", "[", "]", "{", "}", "/", "%"
|
||||
|
||||
def __init__(self, data):
|
||||
str.__init__(self)
|
||||
self = data
|
||||
str.__init__(data)
|
||||
|
||||
def writeToStream(self, stream, encryption_key):
|
||||
stream.write(self)
|
||||
@@ -710,6 +721,12 @@ class RectangleObject(ArrayObject):
|
||||
def setUpperRight(self, value):
|
||||
self[2], self[3] = [self.ensureIsNumber(x) for x in value]
|
||||
|
||||
def getWidth(self):
|
||||
return self.getUpperRight_x() - self.getLowerLeft_x()
|
||||
|
||||
def getHeight(self):
|
||||
return self.getUpperRight_y() - self.getLowerLeft_x()
|
||||
|
||||
lowerLeft = property(getLowerLeft, setLowerLeft, None, None)
|
||||
lowerRight = property(getLowerRight, setLowerRight, None, None)
|
||||
upperLeft = property(getUpperLeft, setUpperLeft, None, None)
|
||||
|
||||
+381
-37
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# vim: sw=4:expandtab:foldmethod=marker
|
||||
#
|
||||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
@@ -39,7 +40,9 @@ It may be a solid base for future PDF file work in Python.
|
||||
__author__ = "Mathieu Fenniak"
|
||||
__author_email__ = "biziqe@mathieu.fenniak.net"
|
||||
|
||||
import math
|
||||
import struct
|
||||
from sys import version_info
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
@@ -51,6 +54,14 @@ import warnings
|
||||
from generic import *
|
||||
from utils import readNonWhitespace, readUntilWhitespace, ConvertFunctionsToVirtualList
|
||||
|
||||
if version_info < ( 2, 4 ):
|
||||
from sets import ImmutableSet as frozenset
|
||||
|
||||
if version_info < ( 2, 5 ):
|
||||
from md5 import md5
|
||||
else:
|
||||
from hashlib import md5
|
||||
|
||||
##
|
||||
# This class supports writing PDF files out, given pages produced by another
|
||||
# class (typically {@link #PdfFileReader PdfFileReader}).
|
||||
@@ -92,6 +103,21 @@ class PdfFileWriter(object):
|
||||
raise ValueError("pdf must be self")
|
||||
return self._objects[ido.idnum - 1]
|
||||
|
||||
##
|
||||
# Common method for inserting or adding a page to this PDF file.
|
||||
#
|
||||
# @param page The page to add to the document. This argument should be
|
||||
# an instance of {@link #PageObject PageObject}.
|
||||
# @param action The function which will insert the page in the dictionnary.
|
||||
# Takes: page list, page to add.
|
||||
def _addPage(self, page, action):
|
||||
assert page["/Type"] == "/Page"
|
||||
page[NameObject("/Parent")] = self._pages
|
||||
page = self._addObject(page)
|
||||
pages = self.getObject(self._pages)
|
||||
action(pages["/Kids"], page)
|
||||
pages[NameObject("/Count")] = NumberObject(pages["/Count"] + 1)
|
||||
|
||||
##
|
||||
# Adds a page to this PDF file. The page is usually acquired from a
|
||||
# {@link #PdfFileReader PdfFileReader} instance.
|
||||
@@ -101,12 +127,64 @@ class PdfFileWriter(object):
|
||||
# @param page The page to add to the document. This argument should be
|
||||
# an instance of {@link #PageObject PageObject}.
|
||||
def addPage(self, page):
|
||||
assert page["/Type"] == "/Page"
|
||||
page[NameObject("/Parent")] = self._pages
|
||||
page = self._addObject(page)
|
||||
self._addPage(page, list.append)
|
||||
|
||||
##
|
||||
# Insert a page in this PDF file. The page is usually acquired from a
|
||||
# {@link #PdfFileReader PdfFileReader} instance.
|
||||
#
|
||||
# @param page The page to add to the document. This argument should be
|
||||
# an instance of {@link #PageObject PageObject}.
|
||||
# @param index Position at which the page will be inserted.
|
||||
def insertPage(self, page, index=0):
|
||||
self._addPage(page, lambda l, p: l.insert(index, p))
|
||||
|
||||
##
|
||||
# Retrieves a page by number from this PDF file.
|
||||
# @return Returns a {@link #PageObject PageObject} instance.
|
||||
def getPage(self, pageNumber):
|
||||
pages = self.getObject(self._pages)
|
||||
pages["/Kids"].append(page)
|
||||
pages[NameObject("/Count")] = NumberObject(pages["/Count"] + 1)
|
||||
# XXX: crude hack
|
||||
return pages["/Kids"][pageNumber].getObject()
|
||||
|
||||
##
|
||||
# Return the number of pages.
|
||||
# @return The number of pages.
|
||||
def getNumPages(self):
|
||||
pages = self.getObject(self._pages)
|
||||
return int(pages[NameObject("/Count")])
|
||||
|
||||
##
|
||||
# Append a blank page to this PDF file and returns it. If no page size
|
||||
# is specified, use the size of the last page; throw
|
||||
# PageSizeNotDefinedError if it doesn't exist.
|
||||
# @param width The width of the new page expressed in default user
|
||||
# space units.
|
||||
# @param height The height of the new page expressed in default user
|
||||
# space units.
|
||||
def addBlankPage(self, width=None, height=None):
|
||||
page = PageObject.createBlankPage(self, width, height)
|
||||
self.addPage(page)
|
||||
return page
|
||||
|
||||
##
|
||||
# Insert a blank page to this PDF file and returns it. If no page size
|
||||
# is specified, use the size of the page in the given index; throw
|
||||
# PageSizeNotDefinedError if it doesn't exist.
|
||||
# @param width The width of the new page expressed in default user
|
||||
# space units.
|
||||
# @param height The height of the new page expressed in default user
|
||||
# space units.
|
||||
# @param index Position to add the page.
|
||||
def insertBlankPage(self, width=None, height=None, index=0):
|
||||
if width is None or height is None and \
|
||||
(self.getNumPages() - 1) >= index:
|
||||
oldpage = self.getPage(index)
|
||||
width = oldpage.mediaBox.getWidth()
|
||||
height = oldpage.mediaBox.getHeight()
|
||||
page = PageObject.createBlankPage(self, width, height)
|
||||
self.insertPage(page, index)
|
||||
return page
|
||||
|
||||
##
|
||||
# Encrypt this PDF file with the PDF Standard encryption handler.
|
||||
@@ -119,7 +197,7 @@ class PdfFileWriter(object):
|
||||
# encryption. When false, 40bit encryption will be used. By default, this
|
||||
# flag is on.
|
||||
def encrypt(self, user_pwd, owner_pwd = None, use_128bit = True):
|
||||
import md5, time, random
|
||||
import time, random
|
||||
if owner_pwd == None:
|
||||
owner_pwd = user_pwd
|
||||
if use_128bit:
|
||||
@@ -133,8 +211,8 @@ class PdfFileWriter(object):
|
||||
# permit everything:
|
||||
P = -1
|
||||
O = ByteStringObject(_alg33(owner_pwd, user_pwd, rev, keylen))
|
||||
ID_1 = md5.new(repr(time.time())).digest()
|
||||
ID_2 = md5.new(repr(random.random())).digest()
|
||||
ID_1 = md5(repr(time.time())).digest()
|
||||
ID_2 = md5(repr(random.random())).digest()
|
||||
self._ID = ArrayObject((ByteStringObject(ID_1), ByteStringObject(ID_2)))
|
||||
if rev == 2:
|
||||
U, key = _alg34(user_pwd, O, P, ID_1)
|
||||
@@ -160,9 +238,28 @@ class PdfFileWriter(object):
|
||||
# @param stream An object to write the file to. The object must support
|
||||
# the write method, and the tell method, similar to a file object.
|
||||
def write(self, stream):
|
||||
import struct, md5
|
||||
import struct
|
||||
|
||||
externalReferenceMap = {}
|
||||
|
||||
# PDF objects sometimes have circular references to their /Page objects
|
||||
# inside their object tree (for example, annotations). Those will be
|
||||
# indirect references to objects that we've recreated in this PDF. To
|
||||
# address this problem, PageObject's store their original object
|
||||
# reference number, and we add it to the external reference map before
|
||||
# we sweep for indirect references. This forces self-page-referencing
|
||||
# trees to reference the correct new object location, rather than
|
||||
# copying in a new copy of the page object.
|
||||
for objIndex in xrange(len(self._objects)):
|
||||
obj = self._objects[objIndex]
|
||||
if isinstance(obj, PageObject) and obj.indirectRef != None:
|
||||
data = obj.indirectRef
|
||||
if not externalReferenceMap.has_key(data.pdf):
|
||||
externalReferenceMap[data.pdf] = {}
|
||||
if not externalReferenceMap[data.pdf].has_key(data.generation):
|
||||
externalReferenceMap[data.pdf][data.generation] = {}
|
||||
externalReferenceMap[data.pdf][data.generation][data.idnum] = IndirectObject(objIndex + 1, 0, self)
|
||||
|
||||
self.stack = []
|
||||
self._sweepIndirectReferences(externalReferenceMap, self._root)
|
||||
del self.stack
|
||||
@@ -181,7 +278,7 @@ class PdfFileWriter(object):
|
||||
pack2 = struct.pack("<i", 0)[:2]
|
||||
key = self._encrypt_key + pack1 + pack2
|
||||
assert len(key) == (len(self._encrypt_key) + 5)
|
||||
md5_hash = md5.new(key).digest()
|
||||
md5_hash = md5(key).digest()
|
||||
key = md5_hash[:min(16, len(self._encrypt_key) + 5)]
|
||||
obj.writeToStream(stream, key)
|
||||
stream.write("\nendobj\n")
|
||||
@@ -487,7 +584,7 @@ class PdfFileReader(object):
|
||||
pages = property(lambda self: ConvertFunctionsToVirtualList(self.getNumPages, self.getPage),
|
||||
None, None)
|
||||
|
||||
def _flatten(self, pages=None, inherit=None):
|
||||
def _flatten(self, pages=None, inherit=None, indirectRef=None):
|
||||
inheritablePageAttributes = (
|
||||
NameObject("/Resources"), NameObject("/MediaBox"),
|
||||
NameObject("/CropBox"), NameObject("/Rotate")
|
||||
@@ -504,14 +601,17 @@ class PdfFileReader(object):
|
||||
if pages.has_key(attr):
|
||||
inherit[attr] = pages[attr]
|
||||
for page in pages["/Kids"]:
|
||||
self._flatten(page.getObject(), inherit)
|
||||
addt = {}
|
||||
if isinstance(page, IndirectObject):
|
||||
addt["indirectRef"] = page
|
||||
self._flatten(page.getObject(), inherit, **addt)
|
||||
elif t == "/Page":
|
||||
for attr,value in inherit.items():
|
||||
# if the page has it's own value, it does not inherit the
|
||||
# parent's value:
|
||||
if not pages.has_key(attr):
|
||||
pages[attr] = value
|
||||
pageObj = PageObject(self)
|
||||
pageObj = PageObject(self, indirectRef)
|
||||
pageObj.update(pages)
|
||||
self.flattenedPages.append(pageObj)
|
||||
|
||||
@@ -554,12 +654,12 @@ class PdfFileReader(object):
|
||||
if not hasattr(self, '_decryption_key'):
|
||||
raise Exception, "file has not been decrypted"
|
||||
# otherwise, decrypt here...
|
||||
import struct, md5
|
||||
import struct
|
||||
pack1 = struct.pack("<i", indirectReference.idnum)[:3]
|
||||
pack2 = struct.pack("<i", indirectReference.generation)[:2]
|
||||
key = self._decryption_key + pack1 + pack2
|
||||
assert len(key) == (len(self._decryption_key) + 5)
|
||||
md5_hash = md5.new(key).digest()
|
||||
md5_hash = md5(key).digest()
|
||||
key = md5_hash[:min(16, len(self._decryption_key) + 5)]
|
||||
retval = self._decryptObject(retval, key)
|
||||
|
||||
@@ -890,11 +990,46 @@ def createRectangleAccessor(name, fallback):
|
||||
##
|
||||
# This class represents a single page within a PDF file. Typically this object
|
||||
# will be created by accessing the {@link #PdfFileReader.getPage getPage}
|
||||
# function of the {@link #PdfFileReader PdfFileReader} class.
|
||||
# function of the {@link #PdfFileReader PdfFileReader} class, but it is
|
||||
# also possible to create an empty page with the createBlankPage static
|
||||
# method.
|
||||
# @param pdf PDF file the page belongs to (optional, defaults to None).
|
||||
class PageObject(DictionaryObject):
|
||||
def __init__(self, pdf):
|
||||
def __init__(self, pdf=None, indirectRef=None):
|
||||
DictionaryObject.__init__(self)
|
||||
self.pdf = pdf
|
||||
# Stores the original indirect reference to this object in its source PDF
|
||||
self.indirectRef = indirectRef
|
||||
|
||||
##
|
||||
# Returns a new blank page.
|
||||
# If width or height is None, try to get the page size from the
|
||||
# last page of pdf. If pdf is None or contains no page, a
|
||||
# PageSizeNotDefinedError is raised.
|
||||
# @param pdf PDF file the page belongs to
|
||||
# @param width The width of the new page expressed in default user
|
||||
# space units.
|
||||
# @param height The height of the new page expressed in default user
|
||||
# space units.
|
||||
def createBlankPage(pdf=None, width=None, height=None):
|
||||
page = PageObject(pdf)
|
||||
|
||||
# Creates a new page (cf PDF Reference 7.7.3.3)
|
||||
page.__setitem__(NameObject('/Type'), NameObject('/Page'))
|
||||
page.__setitem__(NameObject('/Parent'), NullObject())
|
||||
page.__setitem__(NameObject('/Resources'), DictionaryObject())
|
||||
if width is None or height is None:
|
||||
if pdf is not None and pdf.getNumPages() > 0:
|
||||
lastpage = pdf.getPage(pdf.getNumPages() - 1)
|
||||
width = lastpage.mediaBox.getWidth()
|
||||
height = lastpage.mediaBox.getHeight()
|
||||
else:
|
||||
raise utils.PageSizeNotDefinedError()
|
||||
page.__setitem__(NameObject('/MediaBox'),
|
||||
RectangleObject([0, 0, width, height]))
|
||||
|
||||
return page
|
||||
createBlankPage = staticmethod(createBlankPage)
|
||||
|
||||
##
|
||||
# Rotates a page clockwise by increments of 90 degrees.
|
||||
@@ -931,7 +1066,7 @@ class PageObject(DictionaryObject):
|
||||
renameRes[key] = newname
|
||||
newRes[newname] = page2Res[key]
|
||||
elif not newRes.has_key(key):
|
||||
newRes[key] = page2Res[key]
|
||||
newRes[key] = page2Res.raw_get(key)
|
||||
return newRes, renameRes
|
||||
_mergeResources = staticmethod(_mergeResources)
|
||||
|
||||
@@ -957,6 +1092,26 @@ class PageObject(DictionaryObject):
|
||||
return stream
|
||||
_pushPopGS = staticmethod(_pushPopGS)
|
||||
|
||||
def _addTransformationMatrix(contents, pdf, ctm):
|
||||
# adds transformation matrix at the beginning of the given
|
||||
# contents stream.
|
||||
a, b, c, d, e, f = ctm
|
||||
contents = ContentStream(contents, pdf)
|
||||
contents.operations.insert(0, [[FloatObject(a), FloatObject(b),
|
||||
FloatObject(c), FloatObject(d), FloatObject(e),
|
||||
FloatObject(f)], " cm"])
|
||||
return contents
|
||||
_addTransformationMatrix = staticmethod(_addTransformationMatrix)
|
||||
|
||||
##
|
||||
# Returns the /Contents object, or None if it doesn't exist.
|
||||
# /Contents is optionnal, as described in PDF Reference 7.7.3.3
|
||||
def getContents(self):
|
||||
if self.has_key("/Contents"):
|
||||
return self["/Contents"].getObject()
|
||||
else:
|
||||
return None
|
||||
|
||||
##
|
||||
# Merges the content streams of two pages into one. Resource references
|
||||
# (i.e. fonts) are maintained from both pages. The mediabox/cropbox/etc
|
||||
@@ -968,7 +1123,23 @@ class PageObject(DictionaryObject):
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged
|
||||
# into this one.
|
||||
def mergePage(self, page2):
|
||||
self._mergePage(page2)
|
||||
|
||||
##
|
||||
# Actually merges the content streams of two pages into one. Resource
|
||||
# references (i.e. fonts) are maintained from both pages. The
|
||||
# mediabox/cropbox/etc of this page are not altered. The parameter page's
|
||||
# content stream will be added to the end of this page's content stream,
|
||||
# meaning that it will be drawn after, or "on top" of this page.
|
||||
#
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged
|
||||
# into this one.
|
||||
# @param page2transformation A fuction which applies a transformation to
|
||||
# the content stream of page2. Takes: page2
|
||||
# contents stream. Must return: new contents
|
||||
# stream. If omitted, the content stream will
|
||||
# not be modified.
|
||||
def _mergePage(self, page2, page2transformation=None):
|
||||
# First we work on merging the resource dictionaries. This allows us
|
||||
# to find out what symbols in the content streams we might need to
|
||||
# rename.
|
||||
@@ -978,7 +1149,7 @@ class PageObject(DictionaryObject):
|
||||
originalResources = self["/Resources"].getObject()
|
||||
page2Resources = page2["/Resources"].getObject()
|
||||
|
||||
for res in "/ExtGState", "/Font", "/XObject", "/ColorSpace", "/Pattern", "/Shading":
|
||||
for res in "/ExtGState", "/Font", "/XObject", "/ColorSpace", "/Pattern", "/Shading", "/Properties":
|
||||
new, newrename = PageObject._mergeResources(originalResources, page2Resources, res)
|
||||
if new:
|
||||
newResources[NameObject(res)] = new
|
||||
@@ -993,17 +1164,191 @@ class PageObject(DictionaryObject):
|
||||
|
||||
newContentArray = ArrayObject()
|
||||
|
||||
originalContent = self["/Contents"].getObject()
|
||||
newContentArray.append(PageObject._pushPopGS(originalContent, self.pdf))
|
||||
originalContent = self.getContents()
|
||||
if originalContent is not None:
|
||||
newContentArray.append(PageObject._pushPopGS(
|
||||
originalContent, self.pdf))
|
||||
|
||||
page2Content = page2['/Contents'].getObject()
|
||||
page2Content = PageObject._contentStreamRename(page2Content, rename, self.pdf)
|
||||
page2Content = PageObject._pushPopGS(page2Content, self.pdf)
|
||||
newContentArray.append(page2Content)
|
||||
page2Content = page2.getContents()
|
||||
if page2Content is not None:
|
||||
if page2transformation is not None:
|
||||
page2Content = page2transformation(page2Content)
|
||||
page2Content = PageObject._contentStreamRename(
|
||||
page2Content, rename, self.pdf)
|
||||
page2Content = PageObject._pushPopGS(page2Content, self.pdf)
|
||||
newContentArray.append(page2Content)
|
||||
|
||||
self[NameObject('/Contents')] = ContentStream(newContentArray, self.pdf)
|
||||
self[NameObject('/Resources')] = newResources
|
||||
|
||||
##
|
||||
# This is similar to mergePage, but a transformation matrix is
|
||||
# applied to the merged stream.
|
||||
#
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged.
|
||||
# @param ctm A 6 elements tuple containing the operands of the
|
||||
# transformation matrix
|
||||
def mergeTransformedPage(self, page2, ctm):
|
||||
self._mergePage(page2, lambda page2Content:
|
||||
PageObject._addTransformationMatrix(page2Content, page2.pdf, ctm))
|
||||
|
||||
##
|
||||
# This is similar to mergePage, but the stream to be merged is scaled
|
||||
# by appling a transformation matrix.
|
||||
#
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged.
|
||||
# @param factor The scaling factor
|
||||
def mergeScaledPage(self, page2, factor):
|
||||
# CTM to scale : [ sx 0 0 sy 0 0 ]
|
||||
return self.mergeTransformedPage(page2, [factor, 0,
|
||||
0, factor,
|
||||
0, 0])
|
||||
|
||||
##
|
||||
# This is similar to mergePage, but the stream to be merged is rotated
|
||||
# by appling a transformation matrix.
|
||||
#
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged.
|
||||
# @param rotation The angle of the rotation, in degrees
|
||||
def mergeRotatedPage(self, page2, rotation):
|
||||
rotation = math.radians(rotation)
|
||||
return self.mergeTransformedPage(page2,
|
||||
[math.cos(rotation), math.sin(rotation),
|
||||
-math.sin(rotation), math.cos(rotation),
|
||||
0, 0])
|
||||
|
||||
##
|
||||
# This is similar to mergePage, but the stream to be merged is translated
|
||||
# by appling a transformation matrix.
|
||||
#
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged.
|
||||
# @param tx The translation on X axis
|
||||
# @param tx The translation on Y axis
|
||||
def mergeTranslatedPage(self, page2, tx, ty):
|
||||
return self.mergeTransformedPage(page2, [1, 0,
|
||||
0, 1,
|
||||
tx, ty])
|
||||
|
||||
##
|
||||
# This is similar to mergePage, but the stream to be merged is rotated
|
||||
# and scaled by appling a transformation matrix.
|
||||
#
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged.
|
||||
# @param rotation The angle of the rotation, in degrees
|
||||
# @param factor The scaling factor
|
||||
def mergeRotatedScaledPage(self, page2, rotation, scale):
|
||||
rotation = math.radians(rotation)
|
||||
rotating = [[math.cos(rotation), math.sin(rotation),0],
|
||||
[-math.sin(rotation),math.cos(rotation), 0],
|
||||
[0, 0, 1]]
|
||||
scaling = [[scale,0, 0],
|
||||
[0, scale,0],
|
||||
[0, 0, 1]]
|
||||
ctm = utils.matrixMultiply(rotating, scaling)
|
||||
|
||||
return self.mergeTransformedPage(page2,
|
||||
[ctm[0][0], ctm[0][1],
|
||||
ctm[1][0], ctm[1][1],
|
||||
ctm[2][0], ctm[2][1]])
|
||||
|
||||
##
|
||||
# This is similar to mergePage, but the stream to be merged is translated
|
||||
# and scaled by appling a transformation matrix.
|
||||
#
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged.
|
||||
# @param scale The scaling factor
|
||||
# @param tx The translation on X axis
|
||||
# @param tx The translation on Y axis
|
||||
def mergeScaledTranslatedPage(self, page2, scale, tx, ty):
|
||||
translation = [[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[tx,ty,1]]
|
||||
scaling = [[scale,0, 0],
|
||||
[0, scale,0],
|
||||
[0, 0, 1]]
|
||||
ctm = utils.matrixMultiply(scaling, translation)
|
||||
|
||||
return self.mergeTransformedPage(page2, [ctm[0][0], ctm[0][1],
|
||||
ctm[1][0], ctm[1][1],
|
||||
ctm[2][0], ctm[2][1]])
|
||||
|
||||
##
|
||||
# This is similar to mergePage, but the stream to be merged is translated,
|
||||
# rotated and scaled by appling a transformation matrix.
|
||||
#
|
||||
# @param page2 An instance of {@link #PageObject PageObject} to be merged.
|
||||
# @param tx The translation on X axis
|
||||
# @param ty The translation on Y axis
|
||||
# @param rotation The angle of the rotation, in degrees
|
||||
# @param scale The scaling factor
|
||||
def mergeRotatedScaledTranslatedPage(self, page2, rotation, scale, tx, ty):
|
||||
translation = [[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[tx,ty,1]]
|
||||
rotation = math.radians(rotation)
|
||||
rotating = [[math.cos(rotation), math.sin(rotation),0],
|
||||
[-math.sin(rotation),math.cos(rotation), 0],
|
||||
[0, 0, 1]]
|
||||
scaling = [[scale,0, 0],
|
||||
[0, scale,0],
|
||||
[0, 0, 1]]
|
||||
ctm = utils.matrixMultiply(rotating, scaling)
|
||||
ctm = utils.matrixMultiply(ctm, translation)
|
||||
|
||||
return self.mergeTransformedPage(page2, [ctm[0][0], ctm[0][1],
|
||||
ctm[1][0], ctm[1][1],
|
||||
ctm[2][0], ctm[2][1]])
|
||||
|
||||
##
|
||||
# Applys a transformation matrix the page.
|
||||
#
|
||||
# @param ctm A 6 elements tuple containing the operands of the
|
||||
# transformation matrix
|
||||
def addTransformation(self, ctm):
|
||||
originalContent = self.getContents()
|
||||
if originalContent is not None:
|
||||
newContent = PageObject._addTransformationMatrix(
|
||||
originalContent, self.pdf, ctm)
|
||||
newContent = PageObject._pushPopGS(newContent, self.pdf)
|
||||
self[NameObject('/Contents')] = newContent
|
||||
|
||||
##
|
||||
# Scales a page by the given factors by appling a transformation
|
||||
# matrix to its content and updating the page size.
|
||||
#
|
||||
# @param sx The scaling factor on horizontal axis
|
||||
# @param sy The scaling factor on vertical axis
|
||||
def scale(self, sx, sy):
|
||||
self.addTransformation([sx, 0,
|
||||
0, sy,
|
||||
0, 0])
|
||||
self.mediaBox = RectangleObject([
|
||||
float(self.mediaBox.getLowerLeft_x()) * sx,
|
||||
float(self.mediaBox.getLowerLeft_y()) * sy,
|
||||
float(self.mediaBox.getUpperRight_x()) * sx,
|
||||
float(self.mediaBox.getUpperRight_y()) * sy])
|
||||
|
||||
##
|
||||
# Scales a page by the given factor by appling a transformation
|
||||
# matrix to its content and updating the page size.
|
||||
#
|
||||
# @param factor The scaling factor
|
||||
def scaleBy(self, factor):
|
||||
self.scale(factor, factor)
|
||||
|
||||
##
|
||||
# Scales a page to the specified dimentions by appling a
|
||||
# transformation matrix to its content and updating the page size.
|
||||
#
|
||||
# @param width The new width
|
||||
# @param height The new heigth
|
||||
def scaleTo(self, width, height):
|
||||
sx = width / (self.mediaBox.getUpperRight_x() -
|
||||
self.mediaBox.getLowerLeft_x ())
|
||||
sy = height / (self.mediaBox.getUpperRight_y() -
|
||||
self.mediaBox.getLowerLeft_x ())
|
||||
self.scale(sx, sy)
|
||||
|
||||
##
|
||||
# Compresses the size of this page by joining all content streams and
|
||||
# applying a FlateDecode filter.
|
||||
@@ -1012,10 +1357,11 @@ class PageObject(DictionaryObject):
|
||||
# However, it is possible that this function will perform no action if
|
||||
# content stream compression becomes "automatic" for some reason.
|
||||
def compressContentStreams(self):
|
||||
content = self["/Contents"].getObject()
|
||||
if not isinstance(content, ContentStream):
|
||||
content = ContentStream(content, self.pdf)
|
||||
self[NameObject("/Contents")] = content.flateEncode()
|
||||
content = self.getContents()
|
||||
if content is not None:
|
||||
if not isinstance(content, ContentStream):
|
||||
content = ContentStream(content, self.pdf)
|
||||
self[NameObject("/Contents")] = content.flateEncode()
|
||||
|
||||
##
|
||||
# Locate all text drawing commands, in the order they are provided in the
|
||||
@@ -1369,8 +1715,8 @@ def _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encr
|
||||
password = (password + _encryption_padding)[:32]
|
||||
# 2. Initialize the MD5 hash function and pass the result of step 1 as
|
||||
# input to this function.
|
||||
import md5, struct
|
||||
m = md5.new(password)
|
||||
import struct
|
||||
m = md5(password)
|
||||
# 3. Pass the value of the encryption dictionary's /O entry to the MD5 hash
|
||||
# function.
|
||||
m.update(owner_entry)
|
||||
@@ -1394,7 +1740,7 @@ def _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encr
|
||||
# /Length entry.
|
||||
if rev >= 3:
|
||||
for i in range(50):
|
||||
md5_hash = md5.new(md5_hash[:keylen]).digest()
|
||||
md5_hash = md5(md5_hash[:keylen]).digest()
|
||||
# 9. Set the encryption key to the first n bytes of the output from the
|
||||
# final MD5 hash, where n is always 5 for revision 2 but, for revision 3 or
|
||||
# greater, depends on the value of the encryption dictionary's /Length
|
||||
@@ -1436,14 +1782,13 @@ def _alg33_1(password, rev, keylen):
|
||||
password = (password + _encryption_padding)[:32]
|
||||
# 2. Initialize the MD5 hash function and pass the result of step 1 as
|
||||
# input to this function.
|
||||
import md5
|
||||
m = md5.new(password)
|
||||
m = md5(password)
|
||||
# 3. (Revision 3 or greater) Do the following 50 times: Take the output
|
||||
# from the previous MD5 hash and pass it as input into a new MD5 hash.
|
||||
md5_hash = m.digest()
|
||||
if rev >= 3:
|
||||
for i in range(50):
|
||||
md5_hash = md5.new(md5_hash).digest()
|
||||
md5_hash = md5(md5_hash).digest()
|
||||
# 4. Create an RC4 encryption key using the first n bytes of the output
|
||||
# from the final MD5 hash, where n is always 5 for revision 2 but, for
|
||||
# revision 3 or greater, depends on the value of the encryption
|
||||
@@ -1473,8 +1818,7 @@ def _alg35(password, rev, keylen, owner_entry, p_entry, id1_entry, metadata_encr
|
||||
key = _alg32(password, rev, keylen, owner_entry, p_entry, id1_entry)
|
||||
# 2. Initialize the MD5 hash function and pass the 32-byte padding string
|
||||
# shown in step 1 of Algorithm 3.2 as input to this function.
|
||||
import md5
|
||||
m = md5.new()
|
||||
m = md5()
|
||||
m.update(_encryption_padding)
|
||||
# 3. Pass the first element of the file's file identifier array (the value
|
||||
# of the ID entry in the document's trailer dictionary; see Table 3.13 on
|
||||
|
||||
+122
-111
@@ -1,111 +1,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: sw=4:expandtab:foldmethod=marker
|
||||
#
|
||||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""
|
||||
Utility functions for PDF library.
|
||||
"""
|
||||
__author__ = "Mathieu Fenniak"
|
||||
__author_email__ = "biziqe@mathieu.fenniak.net"
|
||||
|
||||
#ENABLE_PSYCO = False
|
||||
#if ENABLE_PSYCO:
|
||||
# try:
|
||||
# import psyco
|
||||
# except ImportError:
|
||||
# ENABLE_PSYCO = False
|
||||
#
|
||||
#if not ENABLE_PSYCO:
|
||||
# class psyco:
|
||||
# def proxy(func):
|
||||
# return func
|
||||
# proxy = staticmethod(proxy)
|
||||
|
||||
def readUntilWhitespace(stream, maxchars=None):
|
||||
txt = ""
|
||||
while True:
|
||||
tok = stream.read(1)
|
||||
if tok.isspace() or not tok:
|
||||
break
|
||||
txt += tok
|
||||
if len(txt) == maxchars:
|
||||
break
|
||||
return txt
|
||||
|
||||
def readNonWhitespace(stream):
|
||||
tok = ' '
|
||||
while tok == '\n' or tok == '\r' or tok == ' ' or tok == '\t':
|
||||
tok = stream.read(1)
|
||||
return tok
|
||||
|
||||
class ConvertFunctionsToVirtualList(object):
|
||||
def __init__(self, lengthFunction, getFunction):
|
||||
self.lengthFunction = lengthFunction
|
||||
self.getFunction = getFunction
|
||||
|
||||
def __len__(self):
|
||||
return self.lengthFunction()
|
||||
|
||||
def __getitem__(self, index):
|
||||
if not isinstance(index, int):
|
||||
raise TypeError, "sequence indices must be integers"
|
||||
len_self = len(self)
|
||||
if index < 0:
|
||||
# support negative indexes
|
||||
index = len_self + index
|
||||
if index < 0 or index >= len_self:
|
||||
raise IndexError, "sequence index out of range"
|
||||
return self.getFunction(index)
|
||||
|
||||
def RC4_encrypt(key, plaintext):
|
||||
S = [i for i in range(256)]
|
||||
j = 0
|
||||
for i in range(256):
|
||||
j = (j + S[i] + ord(key[i % len(key)])) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
i, j = 0, 0
|
||||
retval = ""
|
||||
for x in range(len(plaintext)):
|
||||
i = (i + 1) % 256
|
||||
j = (j + S[i]) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
t = S[(S[i] + S[j]) % 256]
|
||||
retval += chr(ord(plaintext[x]) ^ t)
|
||||
return retval
|
||||
|
||||
class PdfReadError(Exception):
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test RC4
|
||||
out = RC4_encrypt("Key", "Plaintext")
|
||||
print repr(out)
|
||||
pt = RC4_encrypt("Key", out)
|
||||
print repr(pt)
|
||||
# vim: sw=4:expandtab:foldmethod=marker
|
||||
#
|
||||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""
|
||||
Utility functions for PDF library.
|
||||
"""
|
||||
__author__ = "Mathieu Fenniak"
|
||||
__author_email__ = "biziqe@mathieu.fenniak.net"
|
||||
|
||||
#ENABLE_PSYCO = False
|
||||
#if ENABLE_PSYCO:
|
||||
# try:
|
||||
# import psyco
|
||||
# except ImportError:
|
||||
# ENABLE_PSYCO = False
|
||||
#
|
||||
#if not ENABLE_PSYCO:
|
||||
# class psyco:
|
||||
# def proxy(func):
|
||||
# return func
|
||||
# proxy = staticmethod(proxy)
|
||||
|
||||
def readUntilWhitespace(stream, maxchars=None):
|
||||
txt = ""
|
||||
while True:
|
||||
tok = stream.read(1)
|
||||
if tok.isspace() or not tok:
|
||||
break
|
||||
txt += tok
|
||||
if len(txt) == maxchars:
|
||||
break
|
||||
return txt
|
||||
|
||||
def readNonWhitespace(stream):
|
||||
tok = ' '
|
||||
while tok == '\n' or tok == '\r' or tok == ' ' or tok == '\t':
|
||||
tok = stream.read(1)
|
||||
return tok
|
||||
|
||||
class ConvertFunctionsToVirtualList(object):
|
||||
def __init__(self, lengthFunction, getFunction):
|
||||
self.lengthFunction = lengthFunction
|
||||
self.getFunction = getFunction
|
||||
|
||||
def __len__(self):
|
||||
return self.lengthFunction()
|
||||
|
||||
def __getitem__(self, index):
|
||||
if not isinstance(index, int):
|
||||
raise TypeError, "sequence indices must be integers"
|
||||
len_self = len(self)
|
||||
if index < 0:
|
||||
# support negative indexes
|
||||
index = len_self + index
|
||||
if index < 0 or index >= len_self:
|
||||
raise IndexError, "sequence index out of range"
|
||||
return self.getFunction(index)
|
||||
|
||||
def RC4_encrypt(key, plaintext):
|
||||
S = [i for i in range(256)]
|
||||
j = 0
|
||||
for i in range(256):
|
||||
j = (j + S[i] + ord(key[i % len(key)])) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
i, j = 0, 0
|
||||
retval = ""
|
||||
for x in range(len(plaintext)):
|
||||
i = (i + 1) % 256
|
||||
j = (j + S[i]) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
t = S[(S[i] + S[j]) % 256]
|
||||
retval += chr(ord(plaintext[x]) ^ t)
|
||||
return retval
|
||||
|
||||
def matrixMultiply(a, b):
|
||||
return [[sum([float(i)*float(j)
|
||||
for i, j in zip(row, col)]
|
||||
) for col in zip(*b)]
|
||||
for row in a]
|
||||
|
||||
class PyPdfError(Exception):
|
||||
pass
|
||||
|
||||
class PdfReadError(PyPdfError):
|
||||
pass
|
||||
|
||||
class PageSizeNotDefinedError(PyPdfError):
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
# test RC4
|
||||
out = RC4_encrypt("Key", "Plaintext")
|
||||
print repr(out)
|
||||
pt = RC4_encrypt("Key", out)
|
||||
print repr(pt)
|
||||
|
||||
+355
-356
@@ -1,356 +1,355 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import datetime
|
||||
import decimal
|
||||
from generic import PdfObject
|
||||
from xml.dom import getDOMImplementation
|
||||
from xml.dom.minidom import parseString
|
||||
|
||||
RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
DC_NAMESPACE = "http://purl.org/dc/elements/1.1/"
|
||||
XMP_NAMESPACE = "http://ns.adobe.com/xap/1.0/"
|
||||
PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/"
|
||||
XMPMM_NAMESPACE = "http://ns.adobe.com/xap/1.0/mm/"
|
||||
|
||||
# What is the PDFX namespace, you might ask? I might ask that too. It's
|
||||
# a completely undocumented namespace used to place "custom metadata"
|
||||
# properties, which are arbitrary metadata properties with no semantic or
|
||||
# documented meaning. Elements in the namespace are key/value-style storage,
|
||||
# where the element name is the key and the content is the value. The keys
|
||||
# are transformed into valid XML identifiers by substituting an invalid
|
||||
# identifier character with \u2182 followed by the unicode hex ID of the
|
||||
# original character. A key like "my car" is therefore "my\u21820020car".
|
||||
#
|
||||
# \u2182, in case you're wondering, is the unicode character
|
||||
# \u{ROMAN NUMERAL TEN THOUSAND}, a straightforward and obvious choice for
|
||||
# escaping characters.
|
||||
#
|
||||
# Intentional users of the pdfx namespace should be shot on sight. A
|
||||
# custom data schema and sensical XML elements could be used instead, as is
|
||||
# suggested by Adobe's own documentation on XMP (under "Extensibility of
|
||||
# Schemas").
|
||||
#
|
||||
# Information presented here on the /pdfx/ schema is a result of limited
|
||||
# reverse engineering, and does not constitute a full specification.
|
||||
PDFX_NAMESPACE = "http://ns.adobe.com/pdfx/1.3/"
|
||||
|
||||
iso8601 = re.compile("""
|
||||
(?P<year>[0-9]{4})
|
||||
(-
|
||||
(?P<month>[0-9]{2})
|
||||
(-
|
||||
(?P<day>[0-9]+)
|
||||
(T
|
||||
(?P<hour>[0-9]{2}):
|
||||
(?P<minute>[0-9]{2})
|
||||
(:(?P<second>[0-9]{2}(.[0-9]+)?))?
|
||||
(?P<tzd>Z|[-+][0-9]{2}:[0-9]{2})
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
""", re.VERBOSE)
|
||||
|
||||
##
|
||||
# An object that represents Adobe XMP metadata.
|
||||
class XmpInformation(PdfObject):
|
||||
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
docRoot = parseString(self.stream.getData())
|
||||
self.rdfRoot = docRoot.getElementsByTagNameNS(RDF_NAMESPACE, "RDF")[0]
|
||||
self.cache = {}
|
||||
|
||||
def writeToStream(self, stream, encryption_key):
|
||||
self.stream.writeToStream(stream, encryption_key)
|
||||
|
||||
def getElement(self, aboutUri, namespace, name):
|
||||
for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
|
||||
if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri:
|
||||
attr = desc.getAttributeNodeNS(namespace, name)
|
||||
if attr != None:
|
||||
yield attr
|
||||
for element in desc.getElementsByTagNameNS(namespace, name):
|
||||
yield element
|
||||
|
||||
def getNodesInNamespace(self, aboutUri, namespace):
|
||||
for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
|
||||
if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri:
|
||||
for i in range(desc.attributes.length):
|
||||
attr = desc.attributes.item(i)
|
||||
if attr.namespaceURI == namespace:
|
||||
yield attr
|
||||
for child in desc.childNodes:
|
||||
if child.namespaceURI == namespace:
|
||||
yield child
|
||||
|
||||
def _getText(self, element):
|
||||
text = ""
|
||||
for child in element.childNodes:
|
||||
if child.nodeType == child.TEXT_NODE:
|
||||
text += child.data
|
||||
return text
|
||||
|
||||
def _converter_string(value):
|
||||
return value
|
||||
|
||||
def _converter_date(value):
|
||||
m = iso8601.match(value)
|
||||
year = int(m.group("year"))
|
||||
month = int(m.group("month") or "1")
|
||||
day = int(m.group("day") or "1")
|
||||
hour = int(m.group("hour") or "0")
|
||||
minute = int(m.group("minute") or "0")
|
||||
second = decimal.Decimal(m.group("second") or "0")
|
||||
seconds = second.to_integral(decimal.ROUND_FLOOR)
|
||||
milliseconds = (second - seconds) * 1000000
|
||||
tzd = m.group("tzd") or "Z"
|
||||
dt = datetime.datetime(year, month, day, hour, minute, seconds, milliseconds)
|
||||
if tzd != "Z":
|
||||
tzd_hours, tzd_minutes = [int(x) for x in tzd.split(":")]
|
||||
tzd_hours *= -1
|
||||
if tzd_hours < 0:
|
||||
tzd_minutes *= -1
|
||||
dt = dt + datetime.timedelta(hours=tzd_hours, minutes=tzd_minutes)
|
||||
return dt
|
||||
_test_converter_date = staticmethod(_converter_date)
|
||||
|
||||
def _getter_bag(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = []
|
||||
for element in self.getElement("", namespace, name):
|
||||
bags = element.getElementsByTagNameNS(RDF_NAMESPACE, "Bag")
|
||||
if len(bags):
|
||||
for bag in bags:
|
||||
for item in bag.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval.append(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_seq(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = []
|
||||
for element in self.getElement("", namespace, name):
|
||||
seqs = element.getElementsByTagNameNS(RDF_NAMESPACE, "Seq")
|
||||
if len(seqs):
|
||||
for seq in seqs:
|
||||
for item in seq.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval.append(value)
|
||||
else:
|
||||
value = converter(self._getText(element))
|
||||
retval.append(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_langalt(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = {}
|
||||
for element in self.getElement("", namespace, name):
|
||||
alts = element.getElementsByTagNameNS(RDF_NAMESPACE, "Alt")
|
||||
if len(alts):
|
||||
for alt in alts:
|
||||
for item in alt.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval[item.getAttribute("xml:lang")] = value
|
||||
else:
|
||||
retval["x-default"] = converter(self._getText(element))
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_single(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
value = None
|
||||
for element in self.getElement("", namespace, name):
|
||||
if element.nodeType == element.ATTRIBUTE_NODE:
|
||||
value = element.nodeValue
|
||||
else:
|
||||
value = self._getText(element)
|
||||
break
|
||||
if value != None:
|
||||
value = converter(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = value
|
||||
return value
|
||||
return get
|
||||
|
||||
##
|
||||
# Contributors to the resource (other than the authors). An unsorted
|
||||
# array of names.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_contributor = property(_getter_bag(DC_NAMESPACE, "contributor", _converter_string))
|
||||
|
||||
##
|
||||
# Text describing the extent or scope of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_coverage = property(_getter_single(DC_NAMESPACE, "coverage", _converter_string))
|
||||
|
||||
##
|
||||
# A sorted array of names of the authors of the resource, listed in order
|
||||
# of precedence.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_creator = property(_getter_seq(DC_NAMESPACE, "creator", _converter_string))
|
||||
|
||||
##
|
||||
# A sorted array of dates (datetime.datetime instances) of signifigance to
|
||||
# the resource. The dates and times are in UTC.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_date = property(_getter_seq(DC_NAMESPACE, "date", _converter_date))
|
||||
|
||||
##
|
||||
# A language-keyed dictionary of textual descriptions of the content of the
|
||||
# resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_description = property(_getter_langalt(DC_NAMESPACE, "description", _converter_string))
|
||||
|
||||
##
|
||||
# The mime-type of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_format = property(_getter_single(DC_NAMESPACE, "format", _converter_string))
|
||||
|
||||
##
|
||||
# Unique identifier of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_identifier = property(_getter_single(DC_NAMESPACE, "identifier", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array specifying the languages used in the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_language = property(_getter_bag(DC_NAMESPACE, "language", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array of publisher names.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_publisher = property(_getter_bag(DC_NAMESPACE, "publisher", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array of text descriptions of relationships to other
|
||||
# documents.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_relation = property(_getter_bag(DC_NAMESPACE, "relation", _converter_string))
|
||||
|
||||
##
|
||||
# A language-keyed dictionary of textual descriptions of the rights the
|
||||
# user has to this resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_rights = property(_getter_langalt(DC_NAMESPACE, "rights", _converter_string))
|
||||
|
||||
##
|
||||
# Unique identifier of the work from which this resource was derived.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_source = property(_getter_single(DC_NAMESPACE, "source", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array of descriptive phrases or keywrods that specify the
|
||||
# topic of the content of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_subject = property(_getter_bag(DC_NAMESPACE, "subject", _converter_string))
|
||||
|
||||
##
|
||||
# A language-keyed dictionary of the title of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_title = property(_getter_langalt(DC_NAMESPACE, "title", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array of textual descriptions of the document type.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_type = property(_getter_bag(DC_NAMESPACE, "type", _converter_string))
|
||||
|
||||
##
|
||||
# An unformatted text string representing document keywords.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
pdf_keywords = property(_getter_single(PDF_NAMESPACE, "Keywords", _converter_string))
|
||||
|
||||
##
|
||||
# The PDF file version, for example 1.0, 1.3.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
pdf_pdfversion = property(_getter_single(PDF_NAMESPACE, "PDFVersion", _converter_string))
|
||||
|
||||
##
|
||||
# The name of the tool that created the PDF document.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
pdf_producer = property(_getter_single(PDF_NAMESPACE, "Producer", _converter_string))
|
||||
|
||||
##
|
||||
# The date and time the resource was originally created. The date and
|
||||
# time are returned as a UTC datetime.datetime object.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmp_createDate = property(_getter_single(XMP_NAMESPACE, "CreateDate", _converter_date))
|
||||
|
||||
##
|
||||
# The date and time the resource was last modified. The date and time
|
||||
# are returned as a UTC datetime.datetime object.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmp_modifyDate = property(_getter_single(XMP_NAMESPACE, "ModifyDate", _converter_date))
|
||||
|
||||
##
|
||||
# The date and time that any metadata for this resource was last
|
||||
# changed. The date and time are returned as a UTC datetime.datetime
|
||||
# object.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmp_metadataDate = property(_getter_single(XMP_NAMESPACE, "MetadataDate", _converter_date))
|
||||
|
||||
##
|
||||
# The name of the first known tool used to create the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmp_creatorTool = property(_getter_single(XMP_NAMESPACE, "CreatorTool", _converter_string))
|
||||
|
||||
##
|
||||
# The common identifier for all versions and renditions of this resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmpmm_documentId = property(_getter_single(XMPMM_NAMESPACE, "DocumentID", _converter_string))
|
||||
|
||||
##
|
||||
# An identifier for a specific incarnation of a document, updated each
|
||||
# time a file is saved.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmpmm_instanceId = property(_getter_single(XMPMM_NAMESPACE, "InstanceID", _converter_string))
|
||||
|
||||
def custom_properties(self):
|
||||
if not hasattr(self, "_custom_properties"):
|
||||
self._custom_properties = {}
|
||||
for node in self.getNodesInNamespace("", PDFX_NAMESPACE):
|
||||
key = node.localName
|
||||
while True:
|
||||
# see documentation about PDFX_NAMESPACE earlier in file
|
||||
idx = key.find(u"\u2182")
|
||||
if idx == -1:
|
||||
break
|
||||
key = key[:idx] + chr(int(key[idx+1:idx+5], base=16)) + key[idx+5:]
|
||||
if node.nodeType == node.ATTRIBUTE_NODE:
|
||||
value = node.nodeValue
|
||||
else:
|
||||
value = self._getText(node)
|
||||
self._custom_properties[key] = value
|
||||
return self._custom_properties
|
||||
|
||||
##
|
||||
# Retrieves custom metadata properties defined in the undocumented pdfx
|
||||
# metadata schema.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
# @return Returns a dictionary of key/value items for custom metadata
|
||||
# properties.
|
||||
custom_properties = property(custom_properties)
|
||||
|
||||
|
||||
import re
|
||||
import datetime
|
||||
import decimal
|
||||
from generic import PdfObject
|
||||
from xml.dom import getDOMImplementation
|
||||
from xml.dom.minidom import parseString
|
||||
|
||||
RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
DC_NAMESPACE = "http://purl.org/dc/elements/1.1/"
|
||||
XMP_NAMESPACE = "http://ns.adobe.com/xap/1.0/"
|
||||
PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/"
|
||||
XMPMM_NAMESPACE = "http://ns.adobe.com/xap/1.0/mm/"
|
||||
|
||||
# What is the PDFX namespace, you might ask? I might ask that too. It's
|
||||
# a completely undocumented namespace used to place "custom metadata"
|
||||
# properties, which are arbitrary metadata properties with no semantic or
|
||||
# documented meaning. Elements in the namespace are key/value-style storage,
|
||||
# where the element name is the key and the content is the value. The keys
|
||||
# are transformed into valid XML identifiers by substituting an invalid
|
||||
# identifier character with \u2182 followed by the unicode hex ID of the
|
||||
# original character. A key like "my car" is therefore "my\u21820020car".
|
||||
#
|
||||
# \u2182, in case you're wondering, is the unicode character
|
||||
# \u{ROMAN NUMERAL TEN THOUSAND}, a straightforward and obvious choice for
|
||||
# escaping characters.
|
||||
#
|
||||
# Intentional users of the pdfx namespace should be shot on sight. A
|
||||
# custom data schema and sensical XML elements could be used instead, as is
|
||||
# suggested by Adobe's own documentation on XMP (under "Extensibility of
|
||||
# Schemas").
|
||||
#
|
||||
# Information presented here on the /pdfx/ schema is a result of limited
|
||||
# reverse engineering, and does not constitute a full specification.
|
||||
PDFX_NAMESPACE = "http://ns.adobe.com/pdfx/1.3/"
|
||||
|
||||
iso8601 = re.compile("""
|
||||
(?P<year>[0-9]{4})
|
||||
(-
|
||||
(?P<month>[0-9]{2})
|
||||
(-
|
||||
(?P<day>[0-9]+)
|
||||
(T
|
||||
(?P<hour>[0-9]{2}):
|
||||
(?P<minute>[0-9]{2})
|
||||
(:(?P<second>[0-9]{2}(.[0-9]+)?))?
|
||||
(?P<tzd>Z|[-+][0-9]{2}:[0-9]{2})
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
""", re.VERBOSE)
|
||||
|
||||
##
|
||||
# An object that represents Adobe XMP metadata.
|
||||
class XmpInformation(PdfObject):
|
||||
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
docRoot = parseString(self.stream.getData())
|
||||
self.rdfRoot = docRoot.getElementsByTagNameNS(RDF_NAMESPACE, "RDF")[0]
|
||||
self.cache = {}
|
||||
|
||||
def writeToStream(self, stream, encryption_key):
|
||||
self.stream.writeToStream(stream, encryption_key)
|
||||
|
||||
def getElement(self, aboutUri, namespace, name):
|
||||
for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
|
||||
if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri:
|
||||
attr = desc.getAttributeNodeNS(namespace, name)
|
||||
if attr != None:
|
||||
yield attr
|
||||
for element in desc.getElementsByTagNameNS(namespace, name):
|
||||
yield element
|
||||
|
||||
def getNodesInNamespace(self, aboutUri, namespace):
|
||||
for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
|
||||
if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri:
|
||||
for i in range(desc.attributes.length):
|
||||
attr = desc.attributes.item(i)
|
||||
if attr.namespaceURI == namespace:
|
||||
yield attr
|
||||
for child in desc.childNodes:
|
||||
if child.namespaceURI == namespace:
|
||||
yield child
|
||||
|
||||
def _getText(self, element):
|
||||
text = ""
|
||||
for child in element.childNodes:
|
||||
if child.nodeType == child.TEXT_NODE:
|
||||
text += child.data
|
||||
return text
|
||||
|
||||
def _converter_string(value):
|
||||
return value
|
||||
|
||||
def _converter_date(value):
|
||||
m = iso8601.match(value)
|
||||
year = int(m.group("year"))
|
||||
month = int(m.group("month") or "1")
|
||||
day = int(m.group("day") or "1")
|
||||
hour = int(m.group("hour") or "0")
|
||||
minute = int(m.group("minute") or "0")
|
||||
second = decimal.Decimal(m.group("second") or "0")
|
||||
seconds = second.to_integral(decimal.ROUND_FLOOR)
|
||||
milliseconds = (second - seconds) * 1000000
|
||||
tzd = m.group("tzd") or "Z"
|
||||
dt = datetime.datetime(year, month, day, hour, minute, seconds, milliseconds)
|
||||
if tzd != "Z":
|
||||
tzd_hours, tzd_minutes = [int(x) for x in tzd.split(":")]
|
||||
tzd_hours *= -1
|
||||
if tzd_hours < 0:
|
||||
tzd_minutes *= -1
|
||||
dt = dt + datetime.timedelta(hours=tzd_hours, minutes=tzd_minutes)
|
||||
return dt
|
||||
_test_converter_date = staticmethod(_converter_date)
|
||||
|
||||
def _getter_bag(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = []
|
||||
for element in self.getElement("", namespace, name):
|
||||
bags = element.getElementsByTagNameNS(RDF_NAMESPACE, "Bag")
|
||||
if len(bags):
|
||||
for bag in bags:
|
||||
for item in bag.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval.append(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_seq(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = []
|
||||
for element in self.getElement("", namespace, name):
|
||||
seqs = element.getElementsByTagNameNS(RDF_NAMESPACE, "Seq")
|
||||
if len(seqs):
|
||||
for seq in seqs:
|
||||
for item in seq.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval.append(value)
|
||||
else:
|
||||
value = converter(self._getText(element))
|
||||
retval.append(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_langalt(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = {}
|
||||
for element in self.getElement("", namespace, name):
|
||||
alts = element.getElementsByTagNameNS(RDF_NAMESPACE, "Alt")
|
||||
if len(alts):
|
||||
for alt in alts:
|
||||
for item in alt.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval[item.getAttribute("xml:lang")] = value
|
||||
else:
|
||||
retval["x-default"] = converter(self._getText(element))
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_single(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
value = None
|
||||
for element in self.getElement("", namespace, name):
|
||||
if element.nodeType == element.ATTRIBUTE_NODE:
|
||||
value = element.nodeValue
|
||||
else:
|
||||
value = self._getText(element)
|
||||
break
|
||||
if value != None:
|
||||
value = converter(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = value
|
||||
return value
|
||||
return get
|
||||
|
||||
##
|
||||
# Contributors to the resource (other than the authors). An unsorted
|
||||
# array of names.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_contributor = property(_getter_bag(DC_NAMESPACE, "contributor", _converter_string))
|
||||
|
||||
##
|
||||
# Text describing the extent or scope of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_coverage = property(_getter_single(DC_NAMESPACE, "coverage", _converter_string))
|
||||
|
||||
##
|
||||
# A sorted array of names of the authors of the resource, listed in order
|
||||
# of precedence.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_creator = property(_getter_seq(DC_NAMESPACE, "creator", _converter_string))
|
||||
|
||||
##
|
||||
# A sorted array of dates (datetime.datetime instances) of signifigance to
|
||||
# the resource. The dates and times are in UTC.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_date = property(_getter_seq(DC_NAMESPACE, "date", _converter_date))
|
||||
|
||||
##
|
||||
# A language-keyed dictionary of textual descriptions of the content of the
|
||||
# resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_description = property(_getter_langalt(DC_NAMESPACE, "description", _converter_string))
|
||||
|
||||
##
|
||||
# The mime-type of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_format = property(_getter_single(DC_NAMESPACE, "format", _converter_string))
|
||||
|
||||
##
|
||||
# Unique identifier of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_identifier = property(_getter_single(DC_NAMESPACE, "identifier", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array specifying the languages used in the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_language = property(_getter_bag(DC_NAMESPACE, "language", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array of publisher names.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_publisher = property(_getter_bag(DC_NAMESPACE, "publisher", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array of text descriptions of relationships to other
|
||||
# documents.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_relation = property(_getter_bag(DC_NAMESPACE, "relation", _converter_string))
|
||||
|
||||
##
|
||||
# A language-keyed dictionary of textual descriptions of the rights the
|
||||
# user has to this resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_rights = property(_getter_langalt(DC_NAMESPACE, "rights", _converter_string))
|
||||
|
||||
##
|
||||
# Unique identifier of the work from which this resource was derived.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_source = property(_getter_single(DC_NAMESPACE, "source", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array of descriptive phrases or keywrods that specify the
|
||||
# topic of the content of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_subject = property(_getter_bag(DC_NAMESPACE, "subject", _converter_string))
|
||||
|
||||
##
|
||||
# A language-keyed dictionary of the title of the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_title = property(_getter_langalt(DC_NAMESPACE, "title", _converter_string))
|
||||
|
||||
##
|
||||
# An unordered array of textual descriptions of the document type.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
dc_type = property(_getter_bag(DC_NAMESPACE, "type", _converter_string))
|
||||
|
||||
##
|
||||
# An unformatted text string representing document keywords.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
pdf_keywords = property(_getter_single(PDF_NAMESPACE, "Keywords", _converter_string))
|
||||
|
||||
##
|
||||
# The PDF file version, for example 1.0, 1.3.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
pdf_pdfversion = property(_getter_single(PDF_NAMESPACE, "PDFVersion", _converter_string))
|
||||
|
||||
##
|
||||
# The name of the tool that created the PDF document.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
pdf_producer = property(_getter_single(PDF_NAMESPACE, "Producer", _converter_string))
|
||||
|
||||
##
|
||||
# The date and time the resource was originally created. The date and
|
||||
# time are returned as a UTC datetime.datetime object.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmp_createDate = property(_getter_single(XMP_NAMESPACE, "CreateDate", _converter_date))
|
||||
|
||||
##
|
||||
# The date and time the resource was last modified. The date and time
|
||||
# are returned as a UTC datetime.datetime object.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmp_modifyDate = property(_getter_single(XMP_NAMESPACE, "ModifyDate", _converter_date))
|
||||
|
||||
##
|
||||
# The date and time that any metadata for this resource was last
|
||||
# changed. The date and time are returned as a UTC datetime.datetime
|
||||
# object.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmp_metadataDate = property(_getter_single(XMP_NAMESPACE, "MetadataDate", _converter_date))
|
||||
|
||||
##
|
||||
# The name of the first known tool used to create the resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmp_creatorTool = property(_getter_single(XMP_NAMESPACE, "CreatorTool", _converter_string))
|
||||
|
||||
##
|
||||
# The common identifier for all versions and renditions of this resource.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmpmm_documentId = property(_getter_single(XMPMM_NAMESPACE, "DocumentID", _converter_string))
|
||||
|
||||
##
|
||||
# An identifier for a specific incarnation of a document, updated each
|
||||
# time a file is saved.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
xmpmm_instanceId = property(_getter_single(XMPMM_NAMESPACE, "InstanceID", _converter_string))
|
||||
|
||||
def custom_properties(self):
|
||||
if not hasattr(self, "_custom_properties"):
|
||||
self._custom_properties = {}
|
||||
for node in self.getNodesInNamespace("", PDFX_NAMESPACE):
|
||||
key = node.localName
|
||||
while True:
|
||||
# see documentation about PDFX_NAMESPACE earlier in file
|
||||
idx = key.find(u"\u2182")
|
||||
if idx == -1:
|
||||
break
|
||||
key = key[:idx] + chr(int(key[idx+1:idx+5], base=16)) + key[idx+5:]
|
||||
if node.nodeType == node.ATTRIBUTE_NODE:
|
||||
value = node.nodeValue
|
||||
else:
|
||||
value = self._getText(node)
|
||||
self._custom_properties[key] = value
|
||||
return self._custom_properties
|
||||
|
||||
##
|
||||
# Retrieves custom metadata properties defined in the undocumented pdfx
|
||||
# metadata schema.
|
||||
# <p>Stability: Added in v1.12, will exist for all future v1.x releases.
|
||||
# @return Returns a dictionary of key/value items for custom metadata
|
||||
# properties.
|
||||
custom_properties = property(custom_properties)
|
||||
|
||||
|
||||
|
||||
@@ -412,7 +412,8 @@ class report_sxw(report_rml, preprocess.report):
|
||||
report_xml = a(title=title, report_type=report_type, report_rml_content=rml, name=title, attachment=False, header=self.header)
|
||||
finally:
|
||||
report_file.close()
|
||||
report_xml.header = self.header
|
||||
if report_xml.header:
|
||||
report_xml.header = self.header
|
||||
report_type = report_xml.report_type
|
||||
if report_type in ['sxw','odt']:
|
||||
fnct = self.create_source_odt
|
||||
|
||||
@@ -187,7 +187,7 @@ class db(netsvc.ExportService):
|
||||
|
||||
self._set_pg_psw_env_var()
|
||||
|
||||
cmd = ['pg_dump', '--format=c', '--no-owner' , '-w']
|
||||
cmd = ['pg_dump', '--format=c', '--no-owner']
|
||||
if tools.config['db_user']:
|
||||
cmd.append('--username=' + tools.config['db_user'])
|
||||
if tools.config['db_host']:
|
||||
@@ -223,7 +223,7 @@ class db(netsvc.ExportService):
|
||||
|
||||
self._create_empty_database(db_name)
|
||||
|
||||
cmd = ['pg_restore', '--no-owner', '-w']
|
||||
cmd = ['pg_restore', '--no-owner']
|
||||
if tools.config['db_user']:
|
||||
cmd.append('--username=' + tools.config['db_user'])
|
||||
if tools.config['db_host']:
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
import socket
|
||||
import base64
|
||||
import errno
|
||||
import SocketServer
|
||||
from BaseHTTPServer import *
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user