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

collective / collective.documentgenerator / 15898628565

26 Jun 2025 09:48AM UTC coverage: 91.987% (-1.8%) from 93.775%
15898628565

Pull #68

github

chris-adam
Added uninstall profile
Pull Request #68: Added uninstall profile

2181 of 2371 relevant lines covered (91.99%)

0.92 hits per line

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

92.75
/src/collective/documentgenerator/search_replace/searchreplace_panel.py
1
# -*- coding: utf-8 -*-
2
from collections import OrderedDict
1✔
3
from collective.documentgenerator import _
1✔
4
from collective.documentgenerator.content.vocabulary import AllPODTemplateWithFileVocabularyFactory
1✔
5
from collective.documentgenerator.search_replace.pod_template import SearchAndReplacePODTemplates
1✔
6
from imio.helpers import HAS_PLONE_5_AND_MORE
1✔
7
from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper
1✔
8
from plone.app.uuid.utils import uuidToObject
1✔
9
from plone.autoform import directives
1✔
10
from plone.autoform.form import AutoExtensibleForm
1✔
11
from plone.z3cform.widget import SingleCheckBoxFieldWidget
1✔
12
from z3c.form import button
1✔
13
from z3c.form import form
1✔
14
from z3c.form.contentprovider import ContentProviders
1✔
15
from z3c.form.interfaces import IFieldsAndContentProvidersForm
1✔
16
from zope import component
1✔
17
from zope import schema
1✔
18
from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
1✔
19
from zope.contentprovider.provider import ContentProviderBase
1✔
20
from zope.interface import implementer
1✔
21
from zope.interface import Interface
1✔
22
from zope.interface import Invalid
1✔
23
from zope.interface import invariant
1✔
24

25
import re
1✔
26

27

28
if HAS_PLONE_5_AND_MORE:
1✔
29
    from collective.z3cform.datagridfield.datagridfield import DataGridFieldFactory
1✔
30
    from collective.z3cform.datagridfield.row import DictRow
1✔
31
else:
32
    from collective.z3cform.datagridfield import DataGridFieldFactory
×
33
    from collective.z3cform.datagridfield import DictRow
×
34

35

36
class IReplacementRowSchema(Interface):
1✔
37
    """
38
    Schema for DataGridField widget's row of field 'replacements'
39
    """
40

41
    search_expr = schema.TextLine(title=_(u"Search"), required=True, default=u"")
1✔
42
    replace_expr = schema.TextLine(title=_(u"Replace"), required=False, default=u"")
1✔
43

44
    directives.widget("is_regex", SingleCheckBoxFieldWidget)
1✔
45
    is_regex = schema.Bool(
1✔
46
        title=_(u"Regex?"),
47
    )
48

49

50
class IDocumentGeneratorSearchReplacePanelSchema(Interface):
1✔
51
    """
52
    Schema for DocumentGeneratorSearchReplacePanel
53
    """
54

55
    selected_templates = schema.List(
1✔
56
        title=_(u"heading_selected_templates", default=u"Selected templates"),
57
        description=_(u"description_selected_templates", default=u""),
58
        required=False,
59
        default=[],
60
        missing_value=[],
61
        value_type=schema.Choice(source="collective.documentgenerator.AllPODTemplateWithFile"),
62
    )
63
    directives.widget("replacements", DataGridFieldFactory)
1✔
64
    replacements = schema.List(
1✔
65
        title=_(u"Replacements"),
66
        description=_("The replacements that will be made."),
67
        required=False,
68
        value_type=DictRow(schema=IReplacementRowSchema, required=True),
69
    )
70

71
    @invariant
1✔
72
    def has_valid_regexes(data):
1✔
73
        if hasattr(data, "replacements"):
1✔
74
            for i, row in enumerate(data.replacements):
1✔
75
                if row["is_regex"]:
1✔
76
                    try:
1✔
77
                        re.compile(row["search_expr"])
1✔
78
                    except re.error:
1✔
79
                        raise Invalid(_(u'Incorrect regex at row #{0} : "{1}"').format(i + 1, row["search_expr"]))
1✔
80

81

82
class SearchResultProvider(ContentProviderBase):
1✔
83
    """
84
    Search result and replace form is implemented through a content provider.
85
    """
86

87
    template = ViewPageTemplateFile("search_result_form.pt")
1✔
88

89
    def __init__(self, context, request, view):
1✔
90
        super(SearchResultProvider, self).__init__(context, request, view)
1✔
91
        self.view = view
1✔
92
        self.results_table = OrderedDict()
1✔
93

94
    def render(self):
1✔
95
        return self.template()
1✔
96

97
    def is_previewing(self):
1✔
98
        return self.view.is_previewing
1✔
99

100
    def get_results_table(self):
1✔
101
        return self.view.results_table
1✔
102

103
    def get_template_link(self, uid):
1✔
104
        template = uuidToObject(uid)
1✔
105
        return template.absolute_url()
1✔
106

107
    def get_template_breadcrumb(self, uid):
1✔
108
        template = uuidToObject(uid)
1✔
109
        breadcrumb_view = template.restrictedTraverse("breadcrumbs_view")
1✔
110
        title = " / ".join([bc["Title"] for bc in breadcrumb_view.breadcrumbs()]) + " ({})".format(template.id)
1✔
111
        return title
1✔
112

113
    @staticmethod
1✔
114
    def display_diff(result):
1✔
115
        """
116
        Add a <strong> HTML tag with class highlight around start and end indices
117
        """
118
        return result.getDiff(type="xhtml")
1✔
119

120

121
@implementer(IDocumentGeneratorSearchReplacePanelSchema)
1✔
122
class DocumentGeneratorSearchReplacePanelAdapter(object):
1✔
123
    component.adapts(Interface)
1✔
124

125
    def __init__(self, context):
1✔
126
        self.context = context
1✔
127

128

129
@implementer(IFieldsAndContentProvidersForm)
1✔
130
class DocumentGeneratorSearchReplacePanelForm(AutoExtensibleForm, form.Form):
1✔
131
    """
132
    DocumentGenerator Search & Replace control panel form
133
    """
134

135
    schema = IDocumentGeneratorSearchReplacePanelSchema
1✔
136
    label = _(u"Search & Replace")
1✔
137
    description = _(u"Search & replace among all template directives in this Plone site templates")
1✔
138

139
    # display the search result and replace form as content provider
140
    contentProviders = ContentProviders()
1✔
141
    contentProviders["search_result_provider"] = SearchResultProvider
1✔
142
    # defining a contentProvider position is mandatory...
143
    contentProviders["search_result_provider"].position = 2
1✔
144

145
    def __init__(self, context, request):
1✔
146
        self.is_previewing = False
1✔
147
        self.results_table = OrderedDict()
1✔
148
        super(DocumentGeneratorSearchReplacePanelForm, self).__init__(context, request)
1✔
149

150
    @button.buttonAndHandler(_("Replace"), name="replace", condition=lambda form: form.can_replace())
1✔
151
    def handle_replace(self, action):  # pragma: no cover
152
        data, errors = self.extractData()
153
        if errors:
154
            self.status = self.formErrorsMessage
155
            return
156
        self.perform_replacements(data)
157
        return self.render()
158

159
    @button.buttonAndHandler(_("Search"), name="search")
1✔
160
    def handle_search(self, action):  # pragma: no cover
161
        data, errors = self.extractData()
162
        if errors:
163
            self.status = self.formErrorsMessage
164
            return
165
        self.perform_preview(data)
166
        return self.render()
167

168
    @button.buttonAndHandler(_("Cancel"), name="cancel")
1✔
169
    def handle_cancel(self, action):  # pragma: no cover
170
        self.request.response.redirect(
171
            "{context_url}/{view}".format(
172
                context_url=self.context.absolute_url(), view="@@collective.documentgenerator-controlpanel"
173
            )
174
        )
175

176
    def can_replace(self):
1✔
177
        # allow to perform a replace only if we performed a search first or if we are performing the replace
178
        search_done = self.request.get("form.buttons.search", False)
1✔
179
        replacing = self.request.get("selected_templates", False)
1✔
180
        return search_done or replacing
1✔
181

182
    def updateActions(self):  # pragma: no cover
183
        super(DocumentGeneratorSearchReplacePanelForm, self).updateActions()
184
        self.actions["search"].addClass("context")  # Make "Search" button primary
185
        if self.request.get("selected_templates", False):  # Must do a new new search before replacing again
186
            self.actions["replace"].addClass("hidden")
187

188
    def updateWidgets(self, prefix=None):  # pragma: no cover
189
        super(DocumentGeneratorSearchReplacePanelForm, self).updateWidgets(prefix)
190
        self.widgets["replacements"].auto_append = False
191

192
    @staticmethod
1✔
193
    def get_selected_templates(form_data):
1✔
194
        """
195
        Get selected templates from form_data
196
        """
197
        uids = form_data["selected_templates"]
1✔
198
        if "all" in uids:
1✔
199
            voc = AllPODTemplateWithFileVocabularyFactory()
×
200
            uids = [brain.UID for brain in voc._get_all_pod_templates_with_file()]
×
201
        templates = [uuidToObject(template_uuid) for template_uuid in uids]
1✔
202
        return templates
1✔
203

204
    def perform_replacements(self, form_data):
1✔
205
        """
206
        Execute replacements action
207
        """
208
        if len(form_data["replacements"]) == 0:
1✔
209
            self.status = _("Nothing to replace.")
×
210
            return
×
211

212
        templates = self.get_selected_templates(form_data)
1✔
213
        self.results_table = {}
1✔
214

215
        with SearchAndReplacePODTemplates(templates) as replace:
1✔
216
            for row in form_data["replacements"]:
1✔
217
                row["replace_expr"] = row["replace_expr"] or ""
1✔
218
                search_expr = row["search_expr"]
1✔
219
                replace_expr = row["replace_expr"]
1✔
220
                replace_results = replace.replace(search_expr, replace_expr, is_regex=row["is_regex"])
1✔
221

222
                for template_uid, template_result in replace_results.items():
1✔
223
                    if not self.results_table.get(template_uid):
1✔
224
                        self.results_table[template_uid] = template_result
1✔
225
                    else:
226
                        self.results_table[template_uid].extend(template_result)
1✔
227

228
        if len(self.results_table) == 0:
1✔
229
            self.status = _("Nothing found.")
×
230

231
    def perform_preview(self, form_data):
1✔
232
        """
233
        Execute preview action
234
        """
235
        if len(form_data["replacements"]) == 0:
1✔
236
            self.status = _("Nothing to preview.")
×
237
            return
×
238
        templates = self.get_selected_templates(form_data)
1✔
239

240
        self.results_table = {}
1✔
241

242
        with SearchAndReplacePODTemplates(templates) as search_replace:
1✔
243
            for row in form_data["replacements"]:
1✔
244
                search_expr = row["search_expr"]
1✔
245
                search_results = search_replace.search(search_expr, is_regex=row["is_regex"])
1✔
246
                for template_uid, template_result in search_results.items():
1✔
247
                    if not self.results_table.get(template_uid):
1✔
248
                        self.results_table[template_uid] = template_result
1✔
249
                    else:
250
                        self.results_table[template_uid].extend(template_result)
1✔
251
            self.is_previewing = True
1✔
252

253
        if len(self.results_table) == 0:
1✔
254
            self.status = _("Nothing found.")
×
255

256

257
class DocumentGeneratorSearchReplace(ControlPanelFormWrapper):
1✔
258
    """
259
    DocumentGenerator Search & Replace control panel
260
    """
261

262
    form = DocumentGeneratorSearchReplacePanelForm
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