• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

collective / collective.documentgenerator / 9287123047

29 May 2024 01:34PM UTC coverage: 91.912% (+0.5%) from 91.42%
9287123047

Pull #60

github

web-flow
Merge af434ede3 into d7b3cc16d
Pull Request #60: Plone 6 compatibility

71 of 92 new or added lines in 15 files covered. (77.17%)

1 existing line in 1 file now uncovered.

2182 of 2374 relevant lines covered (91.91%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.39
/src/collective/documentgenerator/utils.py
1
# -*- coding: utf-8 -*-
2
from collective.documentgenerator import _
1✔
3
from collective.documentgenerator.config import HAS_PLONE_5
1✔
4
from imio.helpers.security import fplog
1✔
5
from plone import api
1✔
6
from plone.namedfile.file import NamedBlobFile
1✔
7
from Products.CMFCore.utils import getToolByName
1✔
8
from Products.CMFPlone.utils import safe_unicode
1✔
9
from zope import i18n
1✔
10
from zope.component import getMultiAdapter
1✔
11
from zope.component.hooks import getSite
1✔
12
from zope.component.hooks import setSite
1✔
13
from zope.interface import Interface
1✔
14
from zope.interface import Invalid
1✔
15
from zope.lifecycleevent import Attributes
1✔
16
from zope.lifecycleevent import modified
1✔
17

18
import hashlib
1✔
19
import logging
1✔
20
import os
1✔
21
import re
1✔
22
import six
1✔
23
import tempfile
1✔
24

25

26
if six.PY2:
1✔
NEW
27
    from appy.bin.odfclean import Cleaner
×
28
else:
29
    from appy.bin.oclean import Cleaner
1✔
30

31
logger = logging.getLogger('collective.documentgenerator')
1✔
32

33

34
def translate(msgid, domain='collective.documentgenerator'):
1✔
35
    portal = api.portal.get()
1✔
36
    translation = i18n.translate(
1✔
37
        msgid,
38
        domain=domain,
39
        context=portal.REQUEST
40
    )
41
    return translation
1✔
42

43

44
def compute_md5(data):
1✔
45
    md5 = hashlib.md5(data).hexdigest()
1✔
46
    return md5
1✔
47

48

49
def update_templates(templates, profile='', force=False):
1✔
50
    """
51
        function to manage templates update.
52
        # see http://trac.imio.be/trac/ticket/9383 for full implementation
53
        :param list templates: list of tuples containing ('plone-template-path', 'os-template-path')
54
        :param str profile: profile path stored on template (or various identification)
55
        :param bool force: force overrides of templates
56
    """
57
    # Don't use profile now !
58
    portal = api.portal.getSite()
1✔
59
    ret = []
1✔
60
    for (ppath, ospath) in templates:
1✔
61
        ppath = ppath.strip('/ ')
1✔
62
        obj = portal.unrestrictedTraverse(ppath, default=None)
1✔
63
        if not obj:
1✔
64
            logger.warn("The plone template '%s' was not found" % ppath)
1✔
65
            ret.append((ppath, ospath, 'plone path error'))
1✔
66
            continue
1✔
67
        if not obj.odt_file:
1✔
68
            logger.warn("The plone template '%s' doesn't have odt file" % ppath)
×
69
            ret.append((ppath, ospath, 'no odt file'))
×
70
            continue
×
71
        if not os.path.exists(ospath):
1✔
72
            logger.warn("The template file '%s' doesn't exist" % ospath)
1✔
73
            ret.append((ppath, ospath, 'os path error'))
1✔
74
            continue
1✔
75
        with open(ospath, 'rb') as f:
1✔
76
            data = f.read()
1✔
77
            new_md5 = compute_md5(data)
1✔
78
            if obj.initial_md5 == new_md5:
1✔
79
                ret.append((ppath, ospath, 'unchanged'))
1✔
80
                continue
1✔
81
            elif obj.has_been_modified() and not force:
1✔
82
                ret.append((ppath, ospath, 'kept'))
1✔
83
                continue
1✔
84
            obj.initial_md5 = new_md5
1✔
85
            obj.style_modification_md5 = new_md5
1✔
86
            obj.odt_file.data = data
1✔
87
            modified(obj, Attributes(Interface, 'odt_file'))
1✔
88
            ret.append((ppath, ospath, 'replaced'))
1✔
89
    return ret
1✔
90

91

92
def update_dict_with_validation(original_dict, update_dict, error_message=_("Dict update collision on key")):
1✔
93
    for key in update_dict:
1✔
94
        if key in original_dict:
1✔
95
            raise Invalid(_("${error_message} for key = '${key}'",
1✔
96
                            mapping={'error_message': error_message, 'key': key}))
97

98
        original_dict[key] = update_dict[key]
1✔
99

100

101
def safe_encode(value, encoding='utf-8'):
1✔
102
    """
103
        Converts a value to encoding, only when it is not already encoded.
104
    """
NEW
105
    if isinstance(value, six.PY2 and unicode or bytes):  # noqa: F821
×
NEW
106
        return value.encode(encoding)
×
NEW
107
    return value
×
108

109

110
def ulocalized_time(date, long_format=None, time_only=None, custom_format=None,
1✔
111
                    domain='plonelocales', target_language=None, context=None,
112
                    request=None, month_lc=True, day_lc=True):
113
    """
114
        Return for a datetime the string value with week and mont translated.
115
        Take into account %a, %A, %b, %B
116
    """
117
    if not custom_format:
1✔
118
        # use toLocalizedTime
119
        plone = getMultiAdapter((context, request), name=u'plone')
1✔
120
        formatted_date = plone.toLocalizedTime(date, long_format, time_only)
1✔
121
    else:
122
        from Products.CMFPlone.i18nl10n import monthname_msgid
1✔
123
        from Products.CMFPlone.i18nl10n import monthname_msgid_abbr
1✔
124
        from Products.CMFPlone.i18nl10n import weekdayname_msgid
1✔
125
        from Products.CMFPlone.i18nl10n import weekdayname_msgid_abbr
1✔
126
        if request is None:
1✔
127
            portal = api.portal.get()
×
128
            request = portal.REQUEST
×
129
        # first replace parts to translate
130
        custom_format = custom_format.replace('%%', '_p_c_')
1✔
131

132
        conf = {
1✔
133
            'a': {'fct': weekdayname_msgid_abbr, 'fmt': '%w', 'low': day_lc},
134
            'A': {'fct': weekdayname_msgid, 'fmt': '%w', 'low': day_lc},
135
            'b': {'fct': monthname_msgid_abbr, 'fmt': '%m', 'low': month_lc},
136
            'B': {'fct': monthname_msgid, 'fmt': '%m', 'low': month_lc},
137
        }
138
        matches = re.findall(r'%([aAbB])', custom_format)
1✔
139
        for match in sorted(set(matches)):
1✔
140
            # function( int(date.strftime(format) )
141
            msgid = conf[match]['fct'](int(date.strftime(conf[match]['fmt'])))
1✔
142
            repl = i18n.translate(msgid, domain, context=request, target_language=target_language)
1✔
143
            if conf[match]['low']:
1✔
144
                repl = repl.lower()
1✔
145
            custom_format = re.sub('%{}'.format(match), repl, custom_format)
1✔
146

147
        # then format date
148
        custom_format = custom_format.replace('_p_c_', '%%')
1✔
149
        if HAS_PLONE_5:
1✔
150
            formatted_date = date.strftime(custom_format)
1✔
151
        else:
NEW
152
            formatted_date = date.strftime(custom_format.encode('utf8'))
×
153
    return safe_unicode(formatted_date)
1✔
154

155

156
def remove_tmp_file(filename):
1✔
157
    """Do not break if unable to remove temporary file, but log error if any."""
158
    try:
1✔
159
        os.remove(filename)
1✔
160
    except OSError:
×
161
        logger.warn("Could not remove temporary file at {0}".format(filename))
×
162

163

164
def update_oo_config():
1✔
165
    """ Update config following buildout var """
166
    key_template = 'collective.documentgenerator.browser.controlpanel.IDocumentGeneratorControlPanelSchema.{}'
1✔
167
    var = {'oo_server': 'OO_SERVER', 'oo_port_list': 'OO_PORT', 'uno_path': 'PYTHON_UNO'}
1✔
168
    for key in var.keys():
1✔
169
        full_key = key_template.format(key)
1✔
170
        configured_oo_option = api.portal.get_registry_record(full_key)
1✔
171
        env_value = os.getenv(var.get(key, 'NO_ONE'), None)
1✔
172
        if env_value:
1✔
173
            new_oo_option = type(configured_oo_option)(os.getenv(var.get(key, 'NO_ONE'), ''))
1✔
174
            if new_oo_option and new_oo_option != configured_oo_option:
1✔
175
                api.portal.set_registry_record(full_key, new_oo_option)
1✔
176
    logger.info("LibreOffice configuration updated for " + getSite().getId())
1✔
177

178

179
def update_oo_config_after_bigbang(event):
1✔
180
    setSite(event.object)
×
181
    try:
×
182
        update_oo_config()
×
183
    except Exception:
×
184
        logger.error("Update LibreOffice configuration failed", exc_info=1)
×
185

186

187
def get_site_root_relative_path(obj):
1✔
188
    return "/" + '/'.join(
1✔
189
        getToolByName(obj, 'portal_url').getRelativeContentPath(obj)
190
    )
191

192

193
def temporary_file_name(suffix=''):
1✔
194
    tmp_dir = os.getenv('CUSTOM_TMP', None)
1✔
195
    if tmp_dir and not os.path.exists(tmp_dir):
1✔
196
        os.mkdir(tmp_dir)
1✔
197
    return tempfile.mktemp(suffix=suffix, dir=tmp_dir)
1✔
198

199

200
def create_temporary_file(initial_file=None, base_name=''):
1✔
201
    tmp_filename = temporary_file_name(suffix=base_name)
1✔
202
    # create the file in any case
203
    with open(tmp_filename, 'wb') as tmp_file:
1✔
204
        if initial_file:
1✔
205
            tmp_file.write(initial_file.data)
1✔
206
    return tmp_file
1✔
207

208

209
def clean_notes(pod_template):
1✔
210
    """ Use appy.pod Cleaner to clean notes (comments). """
211
    cleaned = 0
1✔
212
    odt_file = pod_template.odt_file
1✔
213
    if odt_file:
1✔
214
        # write file to /tmp to be able to use appy.pod Cleaner
215
        tmp_file = create_temporary_file(odt_file, '-to-clean.odt')
1✔
216
        if six.PY2:
1✔
NEW
217
            cleaner = Cleaner(path=tmp_file.name, verbose=1)
×
218
        else:
219
            cleaner = Cleaner(path=tmp_file.name, silent=False)
1✔
220
        cleaned = cleaner.run()
1✔
221
        if cleaned:
1✔
222
            manually_modified = pod_template.has_been_modified()
1✔
223
            with open(tmp_file.name, 'rb') as res_file:
1✔
224
                # update template
225
                result = NamedBlobFile(
1✔
226
                    data=res_file.read(),
227
                    contentType=odt_file.contentType,
228
                    filename=pod_template.odt_file.filename)
229
            pod_template.odt_file = result
1✔
230
            if not manually_modified:
1✔
231
                pod_template.style_modification_md5 = pod_template.current_md5
1✔
232
            extras = 'pod_template={0} cleaned_parts={1}'.format(
1✔
233
                repr(pod_template), cleaned)
234
            fplog('clean_notes', extras=extras)
1✔
235
        remove_tmp_file(tmp_file.name)
1✔
236

237
    return bool(cleaned)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc