ims.fieldupdater 3.0.0__tar.gz → 3.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. ims.fieldupdater-3.0.1/.gitignore +55 -0
  2. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/PKG-INFO +20 -17
  3. ims.fieldupdater-3.0.1/ims/fieldupdater/__init__.py +4 -0
  4. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/browser/mass.pt +1 -1
  5. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/browser/mass.py +109 -117
  6. ims.fieldupdater-3.0.1/ims/fieldupdater/testing.py +53 -0
  7. ims.fieldupdater-3.0.1/pyproject.toml +132 -0
  8. ims.fieldupdater-3.0.0/MANIFEST.in +0 -2
  9. ims.fieldupdater-3.0.0/README.md +0 -21
  10. ims.fieldupdater-3.0.0/ims/fieldupdater/__init__.py +0 -2
  11. ims.fieldupdater-3.0.0/ims/fieldupdater/testing.py +0 -38
  12. ims.fieldupdater-3.0.0/ims/fieldupdater/tests/__init__.py +0 -1
  13. ims.fieldupdater-3.0.0/ims/fieldupdater/tests/base.py +0 -55
  14. ims.fieldupdater-3.0.0/ims/fieldupdater/tests/interfaces.py +0 -38
  15. ims.fieldupdater-3.0.0/ims/fieldupdater/tests/test_mass.py +0 -145
  16. ims.fieldupdater-3.0.0/ims/fieldupdater/tests/test_uninstall.py +0 -23
  17. ims.fieldupdater-3.0.0/ims.fieldupdater.egg-info/PKG-INFO +0 -17
  18. ims.fieldupdater-3.0.0/ims.fieldupdater.egg-info/SOURCES.txt +0 -26
  19. ims.fieldupdater-3.0.0/ims.fieldupdater.egg-info/dependency_links.txt +0 -1
  20. ims.fieldupdater-3.0.0/ims.fieldupdater.egg-info/entry_points.txt +0 -2
  21. ims.fieldupdater-3.0.0/ims.fieldupdater.egg-info/requires.txt +0 -6
  22. ims.fieldupdater-3.0.0/ims.fieldupdater.egg-info/top_level.txt +0 -1
  23. ims.fieldupdater-3.0.0/pyproject.toml +0 -46
  24. ims.fieldupdater-3.0.0/setup.cfg +0 -7
  25. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/browser/__init__.py +0 -0
  26. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/browser/configure.zcml +0 -0
  27. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/browser/schema.pt +0 -0
  28. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/configure.zcml +0 -0
  29. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/profiles/default/controlpanel.xml +0 -0
  30. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/profiles/default/metadata.xml +0 -0
  31. {ims.fieldupdater-3.0.0 → ims.fieldupdater-3.0.1}/ims/fieldupdater/profiles/uninstall/controlpanel.xml +0 -0
@@ -0,0 +1,55 @@
1
+ *.bak
2
+ # Byte-compiled / optimized / DLL files
3
+ __pycache__/
4
+ *.py[cod]
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ env/
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+
26
+ # PyInstaller
27
+ # Usually these files are written by a python script from a template
28
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
29
+ *.manifest
30
+ *.spec
31
+
32
+ # Installer logs
33
+ pip-log.txt
34
+ pip-delete-this-directory.txt
35
+
36
+ # Unit test / coverage reports
37
+ htmlcov/
38
+ .tox/
39
+ .coverage
40
+ .cache
41
+ nosetests.xml
42
+ coverage.xml
43
+
44
+ # Translations
45
+ *.mo
46
+ *.pot
47
+
48
+ # Django stuff:
49
+ *.log
50
+
51
+ # Sphinx documentation
52
+ docs/_build/
53
+
54
+ # PyBuilder
55
+ target/
@@ -1,17 +1,20 @@
1
- Metadata-Version: 2.1
2
- Name: ims.fieldupdater
3
- Version: 3.0.0
4
- Summary: Update all objects in Plone based on a schema/field strategy
5
- Author-email: Eric Wohnlich <wohnlice@imsweb.com>
6
- License: GPL
7
- Project-URL: homepage, https://git.imsweb.com/plone/ims.fieldupdater
8
- Project-URL: documentation, https://git.imsweb.com/plone/ims.fieldupdater
9
- Project-URL: repository, https://git.imsweb.com/plone/ims.fieldupdater
10
- Classifier: Framework :: Plone :: 6.0
11
- Classifier: Programming Language :: Python
12
- Requires-Python: >=3.8
13
- Requires-Dist: plone>=6.0.4
14
- Provides-Extra: test
15
- Requires-Dist: plone.app.testing; extra == "test"
16
- Requires-Dist: plone.mocktestcase; extra == "test"
17
- Requires-Dist: formencode; extra == "test"
1
+ Metadata-Version: 2.4
2
+ Name: ims.fieldupdater
3
+ Version: 3.0.1
4
+ Summary: Update all objects in Plone based on a schema/field strategy
5
+ Project-URL: homepage, https://git.imsweb.com/plone/ims.fieldupdater
6
+ Project-URL: documentation, https://git.imsweb.com/plone/ims.fieldupdater
7
+ Project-URL: repository, https://git.imsweb.com/plone/ims.fieldupdater
8
+ Author-email: Eric Wohnlich <wohnlice@imsweb.com>
9
+ License: GPL
10
+ Classifier: Framework :: Plone :: 6.0
11
+ Classifier: Framework :: Plone :: 6.1
12
+ Classifier: Programming Language :: Python
13
+ Requires-Python: >=3.8
14
+ Requires-Dist: plone>=6.0.4
15
+ Provides-Extra: test
16
+ Requires-Dist: formencode; extra == 'test'
17
+ Requires-Dist: plone-app-robotframework; extra == 'test'
18
+ Requires-Dist: plone-app-testing; extra == 'test'
19
+ Requires-Dist: plone-mocktestcase; extra == 'test'
20
+ Requires-Dist: pytest-plone; extra == 'test'
@@ -0,0 +1,4 @@
1
+ from zope.i18nmessageid import MessageFactory
2
+
3
+ _ = MessageFactory("ims.fieldupdater")
4
+ __version__ = "3.0.1"
@@ -1,6 +1,6 @@
1
1
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US"
2
2
  lang="en-US"
3
- metal:use-macro="context/prefs_main_template/macros/master"
3
+ metal:use-macro="context/prefs_main_template/macros/master">
4
4
  <tal:icon tal:replace="structure python:icons.tag('plone-statusmessage-info', tag_alt='status', tag_class='statusmessage-icon mb-1 me-2')" />
5
5
  i18n:domain="ims.fieldupdater">
6
6
 
@@ -26,34 +26,36 @@ def get_behav(name):
26
26
 
27
27
 
28
28
  class MassEditForm(BrowserView):
29
- template = ViewPageTemplateFile('mass.pt')
29
+ template = ViewPageTemplateFile("mass.pt")
30
30
  unsupported = None
31
31
 
32
32
  def __call__(self):
33
- if not self.request.form.get('form.button.Merge', '') and not self.request.form.get('form.button.Delete', ''):
33
+ if not self.request.form.get("form.button.Merge", "") and not self.request.form.get("form.button.Delete", ""):
34
34
  return self.template({})
35
35
 
36
- schema = self.request.get('schema', None)
37
- field = self.request.get('field', None)
38
- fkey = self.request.get('fkey', None)
39
- match = self.request.get('match', None)
40
- replacement = self.request.get('replacement_marker', None)
36
+ schema = self.request.get("schema", None)
37
+ field = self.request.get("field", None)
38
+ fkey = self.request.get("fkey", None)
39
+ match = self.request.get("match", None)
40
+ replacement = self.request.get("replacement_marker", None)
41
41
 
42
- if self.request.form.get('form.button.Merge', ''):
42
+ if self.request.form.get("form.button.Merge", ""):
43
43
  if replacement:
44
44
  try:
45
45
  self.replace_term(schema, field, fkey, match)
46
46
  except Exception as e:
47
- api.portal.show_message(message='Failed to validate: %s' % e.__repr__(), request=self.request,
48
- type='error')
47
+ api.portal.show_message(
48
+ message=f"Failed to validate: {e.__repr__()}", request=self.request, type="error"
49
+ )
49
50
  else:
50
- api.portal.show_message(message='Please enter a replacement value.', request=self.request)
51
- elif self.request.form.get('form.button.Delete', ''):
51
+ api.portal.show_message(message="Please enter a replacement value.", request=self.request)
52
+ elif self.request.form.get("form.button.Delete", ""):
52
53
  try:
53
54
  self.delete_term(schema, field, fkey, match)
54
55
  except Exception as e:
55
- api.portal.show_message(message='Failed to validate: %s' % e.__repr__(), request=self.request,
56
- type='error')
56
+ api.portal.show_message(
57
+ message=f"Failed to validate: {e.__repr__()}", request=self.request, type="error"
58
+ )
57
59
 
58
60
  return self.template()
59
61
 
@@ -64,14 +66,14 @@ class MassEditForm(BrowserView):
64
66
  :return: dotted name of interfaces
65
67
  """
66
68
  behaviors = tuple([behav[1].interface.__identifier__ for behav in getUtilitiesFor(IBehavior)])
67
- catalog = api.portal.get_tool('portal_catalog')
68
- interfaces = sorted(list(set(catalog.uniqueValuesFor('object_provides') + behaviors)),
69
- key=lambda term: term.split('.')[-1])
69
+ catalog = api.portal.get_tool("portal_catalog")
70
+ interfaces = sorted(set(catalog.uniqueValuesFor("object_provides") + behaviors),
71
+ key=lambda term: term.split(".")[-1])
70
72
  for interface in interfaces:
71
73
  if get_behav(interface).names():
72
74
  yield {
73
- 'id': interface,
74
- 'title': interface.split('.')[-1],
75
+ "id": interface,
76
+ "title": interface.split(".")[-1],
75
77
  }
76
78
 
77
79
  def schema_matches(self, schema):
@@ -80,45 +82,41 @@ class MassEditForm(BrowserView):
80
82
  :param schema: dotted name interface
81
83
  :return: int
82
84
  """
83
- catalog = api.portal.get_tool('portal_catalog')
84
- if schema in catalog.uniqueValuesFor('object_provides'):
85
+ catalog = api.portal.get_tool("portal_catalog")
86
+ if schema in catalog.uniqueValuesFor("object_provides"):
85
87
  return len(catalog(object_provides=schema))
86
88
  else:
87
- return 'unknown (cannot get a subset for behavior interfaces)'
89
+ return "unknown (cannot get a subset for behavior interfaces)"
88
90
 
89
91
  def get_fields(self):
90
92
  """
91
93
  Get all fields for a schema
92
94
  :return: fields
93
95
  """
94
- schema = self.request.get('schema', None)
96
+ schema = self.request.get("schema", None)
95
97
  if not schema:
96
98
  return
97
99
  interface = get_behav(schema)
98
100
  for name in interface.names():
99
- if interface[name] and hasattr(interface[name], 'title'):
100
- yield {
101
- 'id': name,
102
- 'title': '%s [%s]' % (interface[name].title, name)
103
- }
101
+ if interface[name] and hasattr(interface[name], "title"):
102
+ yield {"id": name, "title": f"{interface[name].title} [{name}]"}
104
103
 
105
104
  def is_dg(self):
106
105
  """
107
106
  DataGridField (collective.z3cform.datagridfield) support. Lists with dicts
108
107
  :return: bool
109
108
  """
110
- schema = self.request.get('schema', None)
111
- field = self.request.get('field', None)
109
+ schema = self.request.get("schema", None)
110
+ field = self.request.get("field", None)
112
111
  if not field or not schema:
113
112
  return
114
113
  interface = get_behav(schema)
115
- return hasattr(interface[field], 'value_type') and isinstance(interface[field].value_type, DictRow)
114
+ return hasattr(interface[field], "value_type") and isinstance(interface[field].value_type, DictRow)
116
115
 
117
116
  def get_dgschema(self):
118
- """ Look up the schema used for the dg field
119
- """
120
- schema = self.request.get('schema', None)
121
- field = self.request.get('field', None)
117
+ """Look up the schema used for the dg field"""
118
+ schema = self.request.get("schema", None)
119
+ field = self.request.get("field", None)
122
120
  if not field or not schema:
123
121
  return
124
122
  interface = get_behav(schema)
@@ -132,31 +130,27 @@ class MassEditForm(BrowserView):
132
130
  dg_schema = self.get_dgschema()
133
131
  for dg_field in dg_schema.names():
134
132
  yield {
135
- 'id': dg_field,
136
- 'title': dg_schema[dg_field].title,
133
+ "id": dg_field,
134
+ "title": dg_schema[dg_field].title,
137
135
  }
138
136
 
139
- def get_values(self):
137
+ def get_values(self): # noqa: C901
140
138
  """
141
- Find all of the current values for objects that provide this schema
139
+ Find all the current values for objects that provide this schema
142
140
 
143
141
  :return: values
144
142
  """
145
143
  self.unsupported = None
146
- schema = self.request.get('schema', None)
147
- field = self.request.get('field', None)
148
- fkey = self.request.get('fkey', None)
144
+ schema = self.request.get("schema", None)
145
+ field = self.request.get("field", None)
146
+ fkey = self.request.get("fkey", None)
149
147
 
150
- if self.is_dg() and not (schema and field and fkey):
151
- return
152
- elif not self.is_dg() and not (schema and field):
148
+ if (self.is_dg() and not (schema and field and fkey)) or (not self.is_dg() and not (schema and field)):
153
149
  return
154
150
  values = set()
155
- catalog = api.portal.get_tool('portal_catalog')
156
- if schema in catalog.uniqueValuesFor('object_provides'):
157
- query = catalog(object_provides=schema)
158
- else:
159
- query = catalog()
151
+ catalog = api.portal.get_tool("portal_catalog")
152
+ query = catalog(object_provides=schema) if schema in catalog.uniqueValuesFor("object_provides") else catalog()
153
+
160
154
  for brain in query:
161
155
  obj = brain.getObject()
162
156
  field_value = getattr(obj, field, None)
@@ -164,19 +158,19 @@ class MassEditForm(BrowserView):
164
158
  continue
165
159
  if field_value and isinstance(field_value, str):
166
160
  values.add(field_value)
167
- elif field_value and isinstance(field_value, tuple) or isinstance(field_value, list):
161
+ elif field_value and isinstance(field_value, tuple | list):
168
162
  for item_value in field_value:
169
- if fkey:
163
+ if fkey: # dg
170
164
  if item_value[fkey] and isinstance(item_value[fkey], str):
171
165
  values.add(item_value[fkey])
172
- else:
166
+ else: # regular list or tuple
173
167
  if item_value and isinstance(item_value, str):
174
168
  values.add(item_value)
175
- elif field_value and (isinstance(field_value, datetime.datetime) or isinstance(field_value, datetime.date)):
169
+ elif field_value and isinstance(field_value, datetime.datetime | datetime.date):
176
170
  values.add(field_value)
177
171
  else:
178
172
  self.unsupported = field_value.__class__.__name__
179
- return sorted(list(values))
173
+ return sorted(values)
180
174
 
181
175
  def results(self):
182
176
  """
@@ -184,10 +178,10 @@ class MassEditForm(BrowserView):
184
178
 
185
179
  :return: brains
186
180
  """
187
- schema = self.request.get('schema', None)
188
- field = self.request.get('field', None)
189
- fkey = self.request.get('fkey', None)
190
- match = self.request.get('match', None)
181
+ schema = self.request.get("schema", None)
182
+ field = self.request.get("field", None)
183
+ fkey = self.request.get("fkey", None)
184
+ match = self.request.get("match", None)
191
185
  if not schema or not field:
192
186
  return
193
187
  if self.is_dg() and not fkey:
@@ -195,24 +189,20 @@ class MassEditForm(BrowserView):
195
189
  if not match:
196
190
  return
197
191
 
198
- catalog = api.portal.get_tool('portal_catalog')
192
+ catalog = api.portal.get_tool("portal_catalog")
199
193
  _results = []
200
- if schema in catalog.uniqueValuesFor('object_provides'):
201
- query = catalog(object_provides=schema)
202
- else:
203
- query = catalog()
194
+ query = catalog(object_provides=schema) if schema in catalog.uniqueValuesFor("object_provides") else catalog()
204
195
  for brain in query:
205
196
  obj = brain.getObject()
206
197
  field_value = getattr(obj, field, None)
207
- if field_value == match and isinstance(field_value, str):
208
- _results.append(brain)
209
- elif (isinstance(field_value, tuple) or isinstance(field_value, list)) and match in field_value:
210
- _results.append(brain)
211
- elif fkey and match in [item_value[fkey] for item_value in field_value]:
212
- _results.append(brain)
213
- elif isinstance(field_value, datetime.date) and DateTime(match).asdatetime().date() == field_value:
214
- _results.append(brain)
215
- elif isinstance(field_value, datetime.date) and DateTime(match).asdatetime() == field_value:
198
+ checks = [
199
+ field_value == match and isinstance(field_value, str), # str
200
+ isinstance(field_value, tuple | list) and match in field_value, # iterator
201
+ fkey and match in [item_value[fkey] for item_value in field_value], # dg
202
+ isinstance(field_value, datetime.date) and DateTime(match).asdatetime().date() == field_value,
203
+ isinstance(field_value, datetime.date) and DateTime(match).asdatetime() == field_value
204
+ ]
205
+ if any(checks):
216
206
  _results.append(brain)
217
207
  return _results
218
208
 
@@ -237,31 +227,36 @@ class MassEditForm(BrowserView):
237
227
  # for some reason some things that should come in as unicode are coming in as strings
238
228
  replacement = IDataConverter(widget).toFieldValue(IDataConverter(widget).toWidgetValue(replacement))
239
229
  if not replacement or replacement is NO_VALUE:
240
- api.portal.show_message(message=_('No replacement value given'), request=self.request, type='error')
230
+ api.portal.show_message(message=_("No replacement value given"), request=self.request, type="error")
241
231
  return
242
232
 
243
233
  results = self.results()
244
234
  for brain in results:
245
- obj = brain.getObject()
246
- field_value = getattr(obj, field, None)
247
-
248
- if isinstance(field_value, str) or \
249
- isinstance(field_value, datetime.date) or isinstance(field_value, datetime.datetime):
250
- self.set_value(obj, schema, field, replacement)
251
- elif isinstance(field_value, tuple) or isinstance(field_value, list):
252
- if fkey:
253
- for item_value in field_value:
254
- if item_value[fkey] == match:
255
- item_value[fkey] = replacement
256
- self.set_value(obj, schema, field, field_value)
235
+ self.set_value_by_type(brain, schema, field, fkey, match, replacement)
236
+ api.portal.show_message(
237
+ message=_(f"Replaced term in {len(results)} records"), request=self.request, type="info"
238
+ )
239
+
240
+ def set_value_by_type(self, brain, schema, field, fkey, match, replacement):
241
+ """ Set value based on field value type """
242
+ obj = brain.getObject()
243
+
244
+ field_value = getattr(obj, field, None)
245
+ if isinstance(field_value, str | datetime.date | datetime.datetime):
246
+ self.set_value(obj, schema, field, replacement)
247
+ elif isinstance(field_value, tuple | list):
248
+ if fkey:
249
+ for item_value in field_value:
250
+ if item_value[fkey] == match:
251
+ item_value[fkey] = replacement
252
+ self.set_value(obj, schema, field, field_value)
253
+ else:
254
+ if replacement in field_value:
255
+ field_value = [item_value for item_value in field_value if item_value != match]
257
256
  else:
258
- if replacement in field_value:
259
- field_value = [item_value for item_value in field_value if item_value != match]
260
- else:
261
- field_value = [item_value == match and replacement or item_value for item_value in field_value]
262
- self.set_value(obj, schema, field, field_value)
263
- api.portal.show_message(message=_('Replaced term in {} records'.format(len(results))),
264
- request=self.request, type='info')
257
+ field_value = [(item_value == match and replacement) or item_value for item_value in
258
+ field_value]
259
+ self.set_value(obj, schema, field, field_value)
265
260
 
266
261
  def delete_term(self, schema, field, fkey, match):
267
262
  """
@@ -278,21 +273,23 @@ class MassEditForm(BrowserView):
278
273
  obj = brain.getObject()
279
274
  field_value = getattr(obj, field, None)
280
275
 
281
- if isinstance(field_value, tuple) or isinstance(field_value, list):
276
+ if isinstance(field_value, tuple | list):
282
277
  if fkey:
283
278
  for item_value in field_value:
284
279
  if item_value[fkey] == match:
285
280
  item_value[fkey] = None
286
281
  # if this was the only value in the row, delete the row
287
- field_value = [item_value for item_value in field_value if
288
- [i for i in list(item_value.values()) if i]]
282
+ field_value = [
283
+ item_value for item_value in field_value if [i for i in list(item_value.values()) if i]
284
+ ]
289
285
  else:
290
286
  field_value = [item_value for item_value in field_value if item_value != match]
291
287
  self.set_value(obj, schema, field, field_value)
292
288
  else:
293
289
  self.set_value(obj, schema, field, None)
294
- api.portal.show_message(message=_('Removed term in {} records'.format(len(self.results()))),
295
- request=self.request, type='info')
290
+ api.portal.show_message(
291
+ message=_(f"Removed term in {len(self.results())} records"), request=self.request, type="info"
292
+ )
296
293
 
297
294
  def set_value(self, obj, dottedname, field, field_value, attempts=0):
298
295
  """
@@ -318,8 +315,9 @@ class MassEditForm(BrowserView):
318
315
  field_value = str(field_value)
319
316
  return self.set_value(obj, dottedname, field, field_value, attempts)
320
317
  else:
321
- api.portal.show_message(message='Failed to validate: %s' % e.__repr__(), request=self.request,
322
- type='error')
318
+ api.portal.show_message(
319
+ message=f"Failed to validate: {e.__repr__()}", request=self.request, type="error"
320
+ )
323
321
  else:
324
322
  setattr(obj, field, field_value)
325
323
  notify(ObjectModifiedEvent(obj))
@@ -335,36 +333,30 @@ class MassEditForm(BrowserView):
335
333
  :return: widget
336
334
  """
337
335
  if self.is_dg():
338
- fkey = self.request['fkey']
336
+ fkey = self.request["fkey"]
339
337
  field = self.get_dgschema()[fkey]
340
338
  else:
341
- schema = self.request.get('schema', None)
339
+ schema = self.request.get("schema", None)
342
340
  schema = get_behav(schema)
343
- field = self.request.get('field', None)
341
+ field = self.request.get("field", None)
344
342
  field = schema[field]
345
- if hasattr(field, 'value_type'):
343
+ if hasattr(field, "value_type"):
346
344
  field = field.value_type
347
345
 
348
346
  widget = getMultiAdapter((field, self.request), IFieldWidget)
349
- widget.name = 'replacement'
347
+ widget.name = "replacement"
350
348
  widget.update()
351
349
  return widget
352
350
 
353
351
 
354
352
  class SchemaFinderForm(BrowserView):
355
353
  def get_types(self):
356
- catalog = api.portal.get_tool('portal_catalog')
357
- return sorted(catalog.uniqueValuesFor('portal_type'))
354
+ catalog = api.portal.get_tool("portal_catalog")
355
+ return sorted(catalog.uniqueValuesFor("portal_type"))
358
356
 
359
357
  def schemas(self):
360
- content_type = self.request['content_type']
361
- type_info = api.portal.get_tool('portal_types').getTypeInfo(content_type)
362
- yield {
363
- 'id': type_info.schema,
364
- 'title': type_info.schema.split('.')[-1]
365
- }
358
+ content_type = self.request["content_type"]
359
+ type_info = api.portal.get_tool("portal_types").getTypeInfo(content_type)
360
+ yield {"id": type_info.schema, "title": type_info.schema.split(".")[-1]}
366
361
  for behav in type_info.behaviors:
367
- yield {
368
- 'id': behav,
369
- 'title': behav.split('.')[-1]
370
- }
362
+ yield {"id": behav, "title": behav.split(".")[-1]}
@@ -0,0 +1,53 @@
1
+ from plone.app.contenttypes.testing import PLONE_APP_CONTENTTYPES_FIXTURE
2
+ from plone.app.robotframework.testing import REMOTE_LIBRARY_BUNDLE_FIXTURE
3
+ from plone.app.testing import FunctionalTesting
4
+ from plone.app.testing import IntegrationTesting
5
+ from plone.app.testing import PloneSandboxLayer
6
+ from plone.app.testing import applyProfile
7
+ from plone.protect import auto
8
+ from plone.testing.zope import WSGI_SERVER_FIXTURE
9
+
10
+ import ims.fieldupdater
11
+
12
+ ORIGINAL_CSRF_DISABLED = auto.CSRF_DISABLED
13
+
14
+
15
+ class FieldUpdaterLayer(PloneSandboxLayer):
16
+ defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,)
17
+
18
+ def setUpZope(self, app, configurationContext):
19
+ auto.CSRF_DISABLED = True
20
+ self.loadZCML(package=ims.fieldupdater)
21
+
22
+ def tearDownZope(self, app):
23
+ auto.CSRF_DISABLED = ORIGINAL_CSRF_DISABLED
24
+
25
+ def setUpPloneSite(self, portal):
26
+ applyProfile(portal, "ims.fieldupdater:default")
27
+
28
+
29
+ FIXTURE = FieldUpdaterLayer()
30
+
31
+ INTEGRATION_TESTING = IntegrationTesting(
32
+ bases=(FIXTURE,),
33
+ name="FieldUpdaterLayer:IntegrationTesting",
34
+ )
35
+
36
+ FUNCTIONAL_TESTING = FunctionalTesting(
37
+ bases=(FIXTURE, WSGI_SERVER_FIXTURE),
38
+ name="FieldUpdaterLayer:FunctionalTesting",
39
+ )
40
+
41
+ RESTAPI_TESTING = FunctionalTesting(
42
+ bases=(FIXTURE, WSGI_SERVER_FIXTURE),
43
+ name="FieldUpdaterLayer:RestAPITesting",
44
+ )
45
+
46
+ ACCEPTANCE_TESTING = FunctionalTesting(
47
+ bases=(
48
+ FIXTURE,
49
+ REMOTE_LIBRARY_BUNDLE_FIXTURE,
50
+ WSGI_SERVER_FIXTURE,
51
+ ),
52
+ name="FieldUpdaterLayer:AcceptanceTesting",
53
+ )
@@ -0,0 +1,132 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ims.fieldupdater"
7
+ dynamic = ["version"]
8
+ requires-python = ">=3.8"
9
+ description = "Update all objects in Plone based on a schema/field strategy"
10
+ classifiers = [
11
+ "Framework :: Plone :: 6.0",
12
+ "Framework :: Plone :: 6.1",
13
+ "Programming Language :: Python",
14
+ ]
15
+ authors = [
16
+ { name = "Eric Wohnlich", email = "wohnlice@imsweb.com" }
17
+ ]
18
+ license = { text = "GPL" }
19
+
20
+ dependencies = [
21
+ "plone>=6.0.4"
22
+ ]
23
+
24
+ [project.urls]
25
+ homepage = "https://git.imsweb.com/plone/ims.fieldupdater"
26
+ documentation = "https://git.imsweb.com/plone/ims.fieldupdater"
27
+ repository = "https://git.imsweb.com/plone/ims.fieldupdater"
28
+
29
+ [project.optional-dependencies]
30
+ test = ["plone.app.testing", "plone.mocktestcase", "formencode", "pytest-plone", "plone.app.robotframework"]
31
+
32
+ [project.entry-points."plone.autoinclude.plugin"]
33
+ target = "plone"
34
+
35
+ [tool.bandit]
36
+ exclude_dirs = ["*/tests/*"]
37
+ skips = ["B313", "B314", "B315", "B316", "B317", "B318", "B319", "B320", "B410", "B404", "B603"]
38
+
39
+ [tool.coverage.report]
40
+ include = ["ims/fieldupdater/*"]
41
+ omit = ["venv", "*/test*", "*upgrades.py"]
42
+ [tool.hatch.version]
43
+ path = "ims/fieldupdater/__init__.py"
44
+
45
+ [tool.hatch.build]
46
+ strict-naming = false
47
+ packages = ["ims"]
48
+
49
+ [tool.hatch.envs.default.scripts]
50
+ test = "pytest {args:tests}"
51
+ test-cov = "coverage run -m pytest {args:tests}"
52
+ cov-report = "coverage html"
53
+ cov = [
54
+ "test-cov",
55
+ "cov-report",
56
+ ]
57
+
58
+ [tool.towncrier]
59
+ directory = "changes"
60
+ filename = "CHANGES.md"
61
+ package = "ims.fieldupdater"
62
+ issue_format = "[{issue}]: https://squishlist.com/ims/plone/{issue}"
63
+
64
+ [tool.towncrier.fragment.feature]
65
+ [tool.towncrier.fragment.bugfix]
66
+ [tool.towncrier.fragment.docs]
67
+ [tool.towncrier.fragment.breaking]
68
+ name = "Major Change"
69
+ check = true
70
+
71
+ [tool.towncrier.fragment.chore]
72
+ name = "Other tasks"
73
+ showcontent = false
74
+ check = false
75
+
76
+ [tool.ruff]
77
+ target-version = "py311"
78
+ line-length = 120
79
+ fix = true
80
+ lint.select = [
81
+ # flake8-2020
82
+ "YTT",
83
+ # flake8-bandit
84
+ "S",
85
+ # flake8-bugbear
86
+ "B",
87
+ # flake8-builtins
88
+ "A",
89
+ # flake8-comprehensions
90
+ "C4",
91
+ # flake8-debugger
92
+ "T10",
93
+ # flake8-simplify
94
+ "SIM",
95
+ # mccabe
96
+ "C90",
97
+ # pycodestyle
98
+ "E", "W",
99
+ # pyflakes
100
+ "F",
101
+ # pygrep-hooks
102
+ "PGH",
103
+ # pyupgrade
104
+ "UP",
105
+ # ruff
106
+ "RUF",
107
+ ]
108
+ lint.ignore = [
109
+ # DoNotAssignLambda
110
+ "E731",
111
+ "S602",
112
+ "S314",
113
+ "S324",
114
+ "SIM105"
115
+ ]
116
+
117
+ [tool.ruff.format]
118
+ preview = true
119
+
120
+ [tool.ruff.lint.per-file-ignores]
121
+ "tests/*" = ["S101"]
122
+
123
+ [tool.pytest.ini_options]
124
+ testpaths = ["tests"]
125
+ filterwarnings = [
126
+ "ignore::DeprecationWarning:Products.*",
127
+ "ignore::DeprecationWarning:plone.*",
128
+ "ignore::DeprecationWarning:z3c.*",
129
+ "ignore::DeprecationWarning:zope.*",
130
+ "ignore::DeprecationWarning:webob.*",
131
+ "ignore::DeprecationWarning:pkg_resources.*",
132
+ ]
@@ -1,2 +0,0 @@
1
- recursive-include ims *
2
- global-exclude *pyc
@@ -1,21 +0,0 @@
1
- # ims.fieldupdater
2
-
3
- This project was inspired by Products.KeywordManager and applies some of its ideas to replacing schema field values
4
- on content. Unlike that package, this one is based on schema field and not KeywordIndex. This allows you to change
5
- different kinds of values whether or not they are indexed, but it is also a more expensive process.
6
-
7
- ## Process
8
- - Select a schema. This is an object_provides value and/or a registered Dexterity behavior
9
- - Select a field
10
- - (DataGridField only - select one of the DictRow keys)
11
- - Select one of the existing values on the site to constitute a match
12
- - Enter a new value to replace the match (if replacing)
13
- - Delete or replace
14
-
15
- ## The power of zope.schema
16
-
17
- One of the nice things about zope.schema is that once we know which field
18
- we are changing, we can look it up in the schema and use it for
19
- - rendering an input widget
20
- - data extraction/parsing
21
- - validation
@@ -1,2 +0,0 @@
1
- from zope.i18nmessageid import MessageFactory
2
- _ = MessageFactory('ims.fieldupdater')
@@ -1,38 +0,0 @@
1
- import ims.fieldupdater
2
- from plone.app.testing import PloneSandboxLayer, IntegrationTesting, FunctionalTesting, applyProfile, PLONE_FIXTURE
3
-
4
- has_dgf = True
5
- try:
6
- import collective.z3cform.datagridfield
7
- except ImportError:
8
- has_dgf = False
9
-
10
-
11
- class FieldUpdaterSiteLayer(PloneSandboxLayer):
12
- defaultBases = (PLONE_FIXTURE,)
13
-
14
- def setUpZope(self, app, configuration_context):
15
- # Load any other ZCML that is required for your tests.
16
- # The z3c.autoinclude feature is disabled in the Plone fixture base
17
- # layer.
18
- if has_dgf:
19
- self.loadZCML(package=collective.z3cform.datagridfield)
20
- self.loadZCML(package=ims.fieldupdater)
21
-
22
- def setUpPloneSite(self, portal):
23
- if has_dgf:
24
- applyProfile(portal, 'collective.z3cform.datagridfield:default')
25
- applyProfile(portal, 'ims.fieldupdater:default')
26
-
27
-
28
- FIELD_UPDATER_SITE_FIXTURE = FieldUpdaterSiteLayer()
29
-
30
- INTEGRATION = IntegrationTesting(
31
- bases=(FIELD_UPDATER_SITE_FIXTURE,),
32
- name="ims.fieldupdater:Integration"
33
- )
34
-
35
- FUNCTIONAL = FunctionalTesting(
36
- bases=(FIELD_UPDATER_SITE_FIXTURE,),
37
- name="ims.fieldupdater:Functional"
38
- )
@@ -1,55 +0,0 @@
1
- import unittest
2
-
3
- import transaction
4
- from plone.app.testing import setRoles, TEST_USER_ID, SITE_OWNER_NAME, SITE_OWNER_PASSWORD
5
- from plone.testing.zope import Browser
6
- from zope.interface.declarations import directlyProvides
7
-
8
- from .interfaces import IMassEditTest
9
- from .. import testing
10
-
11
- try:
12
- from Products.CMFCore.indexing import processQueue
13
- except ImportError:
14
- def processQueue():
15
- pass
16
-
17
-
18
- class UnitTestCase(unittest.TestCase):
19
- def setUp(self):
20
- pass
21
-
22
-
23
- class IntegrationTestCase(unittest.TestCase):
24
- layer = testing.INTEGRATION
25
-
26
- def setUp(self):
27
- super(IntegrationTestCase, self).setUp()
28
- self.portal = self.layer['portal']
29
- self.request = self.layer
30
- setRoles(self.portal, TEST_USER_ID, ['Manager'])
31
- self.view = self.portal.restrictedTraverse('@@mass-edit')
32
- self.portal.invokeFactory('Document', 'page1')
33
- self.portal.invokeFactory('Document', 'page2')
34
-
35
- self.page1 = self.portal['page1']
36
- self.page2 = self.portal['page2']
37
- directlyProvides(self.page1, IMassEditTest)
38
- directlyProvides(self.page2, IMassEditTest)
39
- self.page1.reindexObject()
40
- self.page2.reindexObject()
41
- processQueue()
42
-
43
-
44
- class FunctionalTestCase(IntegrationTestCase):
45
- layer = testing.FUNCTIONAL
46
-
47
- def setUp(self):
48
- super(FunctionalTestCase, self).setUp()
49
- self.browser = Browser(self.layer['app'])
50
- self.browser.handleErrors = False
51
- self.browser.addHeader(
52
- 'Authorization',
53
- 'Basic %s:%s' % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD,)
54
- )
55
- transaction.commit()
@@ -1,38 +0,0 @@
1
- from zope.interface import Interface
2
- from zope.schema import List, TextLine, Datetime, Date, Choice
3
- from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
4
-
5
- list_field_opts = SimpleVocabulary([
6
- SimpleTerm(value='fermi', title='Fermi'),
7
- SimpleTerm(value='einstein', title='Einstein'),
8
- SimpleTerm(value='bohr', title='Bohr'),
9
- SimpleTerm(value='heisenberg', title='Heisenberg'),
10
- SimpleTerm(value='hawking', title='Hawking'),
11
- ])
12
-
13
-
14
- class IMassEditTest(Interface):
15
- list_field = List(
16
- title='List field',
17
- value_type=TextLine(),
18
- )
19
- list_choice_field = List(
20
- title='List choice field',
21
- value_type=Choice(
22
- vocabulary=list_field_opts,
23
- )
24
- )
25
- text_field = TextLine(
26
- title='Text field',
27
- required=False,
28
- )
29
- text_field_required = TextLine(
30
- title='Text field',
31
- required=True
32
- )
33
- date_time_field = Datetime(
34
- title='Datetime field',
35
- )
36
- date_field = Date(
37
- title='Date field',
38
- )
@@ -1,145 +0,0 @@
1
- from zope.schema.interfaces import RequiredMissing
2
-
3
- from . import base
4
- from .interfaces import IMassEditTest
5
-
6
-
7
- class TestMassIntegration(base.IntegrationTestCase):
8
- def test_schemas(self):
9
- self.assertIn({'id': IMassEditTest.__identifier__, 'title': 'IMassEditTest'}, self.view.get_schemas())
10
-
11
- def test_list_replace(self):
12
- self.page1.list_field = ['einstein', 'bohr']
13
- self.page2.list_field = ['fermi', 'heisenberg']
14
- match = 'einstein'
15
- field = 'list_field'
16
- replacement = 'hawking'
17
- schema = IMassEditTest.__identifier__
18
- self.view.request['schema'] = schema
19
- self.view.request['field'] = field
20
- self.view.request['match'] = match
21
- self.view.request['replacement'] = replacement
22
- self.view.replace_term(schema, field, None, match)
23
- self.assertEqual(self.page1.list_field, ['hawking', 'bohr'])
24
- self.assertEqual(self.page2.list_field, ['fermi', 'heisenberg']) # sanity - no change
25
-
26
- def test_list_delete(self):
27
- self.page1.list_field = ['einstein', 'bohr']
28
- self.page2.list_field = ['fermi', 'heisenberg']
29
- match = 'einstein'
30
- field = 'list_field'
31
- schema = IMassEditTest.__identifier__
32
- self.view.request['schema'] = schema
33
- self.view.request['field'] = field
34
- self.view.request['match'] = match
35
- self.view.delete_term(schema, field, None, match)
36
- self.assertEqual(self.page1.list_field, ['bohr'])
37
- self.assertEqual(self.page2.list_field, ['fermi', 'heisenberg']) # sanity - no change
38
-
39
- def test_list_replace_wrong_type(self):
40
- """ str converted to unicode """
41
- self.page1.list_field = ['einstein', 'bohr']
42
- self.page2.list_field = ['fermi', 'heisenberg']
43
- match = 'einstein'
44
- field = 'list_field'
45
- replacement = 'hawking'
46
- schema = IMassEditTest.__identifier__
47
- self.view.request['schema'] = schema
48
- self.view.request['field'] = field
49
- self.view.request['match'] = match
50
- self.view.request['replacement'] = replacement
51
- self.view.replace_term(schema, field, None, match)
52
- self.assertEqual(self.page1.list_field, ['hawking', 'bohr'])
53
-
54
- def test_list_choice_replace(self):
55
- """ A term outside of vocab will come in as NO_VALUE and result in no change """
56
- self.page1.list_choice_field = ['einstein', 'bohr']
57
- self.page2.list_choice_field = ['fermi', 'heisenberg']
58
- match = 'einstein'
59
- field = 'list_choice_field'
60
- replacement = 'hawking'
61
- schema = IMassEditTest.__identifier__
62
- self.view.request['schema'] = schema
63
- self.view.request['field'] = field
64
- self.view.request['match'] = match
65
- self.view.request['replacement'] = replacement
66
- self.view.replace_term(schema, field, None, match)
67
- self.assertEqual(self.page1.list_choice_field, ['hawking', 'bohr'])
68
- self.assertEqual(self.page2.list_choice_field, ['fermi', 'heisenberg']) # sanity - no change
69
-
70
- def test_list_choice_replace_invalid(self):
71
- self.page1.list_choice_field = ['einstein', 'bohr']
72
- self.page2.list_choice_field = ['fermi', 'heisenberg']
73
- match = 'einstein'
74
- field = 'list_choice_field'
75
- replacement = 'dirac'
76
- schema = IMassEditTest.__identifier__
77
- self.view.request['schema'] = schema
78
- self.view.request['field'] = field
79
- self.view.request['match'] = match
80
- self.view.request['replacement'] = replacement
81
- self.view.replace_term(schema, field, None, match)
82
- self.assertNotIn('dirac', self.page1.list_choice_field)
83
-
84
- def test_textline_replace(self):
85
- self.page1.text_field = 'einstein'
86
- match = 'einstein'
87
- field = 'text_field'
88
- replacement = 'hawking'
89
- schema = IMassEditTest.__identifier__
90
- self.view.request['schema'] = schema
91
- self.view.request['field'] = field
92
- self.view.request['match'] = match
93
- self.view.request['replacement'] = replacement
94
- self.view.replace_term(schema, field, None, match)
95
- self.assertEqual(self.page1.text_field, 'hawking')
96
-
97
- def test_textline_delete(self):
98
- self.page1.text_field = 'einstein'
99
- match = 'einstein'
100
- field = 'text_field'
101
- schema = IMassEditTest.__identifier__
102
- self.view.request['schema'] = schema
103
- self.view.request['field'] = field
104
- self.view.request['match'] = match
105
- self.view.delete_term(schema, field, None, match)
106
- self.assertEqual(self.page1.text_field, None)
107
-
108
- def test_textline_delete_required(self):
109
- self.page1.text_field_required = 'einstein'
110
- match = 'einstein'
111
- field = 'text_field_required'
112
- schema = IMassEditTest.__identifier__
113
- self.view.request['schema'] = schema
114
- self.view.request['field'] = field
115
- self.view.request['match'] = match
116
- self.assertRaises(RequiredMissing, self.view.delete_term, schema, field, None, match)
117
-
118
- def test_unicode_conversion(self):
119
- """ The widget should really handle this, but we do have this as a failsafe """
120
- self.page1.text_field = 'einstein'
121
- match = 'einstein'
122
- field = 'text_field'
123
- replacement = 'hawking'
124
- schema = IMassEditTest.__identifier__
125
- self.view.request['schema'] = schema
126
- self.view.request['field'] = field
127
- self.view.request['match'] = match
128
- self.view.request['replacement'] = replacement
129
- self.view.replace_term(schema, field, None, match)
130
- self.assertEqual(self.page1.text_field, 'hawking')
131
- self.assertIsInstance(self.page1.text_field, str)
132
-
133
-
134
- class TestMassFunctional(base.FunctionalTestCase):
135
- def test_mass_edit(self):
136
- self.page1.list_choice_field = 'einstein'
137
- self.browser.open(self.portal.absolute_url() + '/@@mass-edit?schema=' +
138
- IMassEditTest.__identifier__ + '&field=list_choice_field&match=')
139
- # ctrl = self.browser.getControl
140
- # ctrl(name='form.buttons.search').click()
141
-
142
-
143
- def test_suite():
144
- import unittest
145
- return unittest.defaultTestLoader.loadTestsFromName(__name__)
@@ -1,23 +0,0 @@
1
- from Products.CMFPlone.utils import get_installer
2
- from plone import api
3
- from plone.app.testing import TEST_USER_ID
4
- from plone.app.testing import setRoles
5
-
6
- from . import base
7
- from .. import testing
8
-
9
-
10
- class TestUninstall(base.IntegrationTestCase):
11
- layer = testing.INTEGRATION
12
-
13
- def setUp(self):
14
- self.portal = self.layer['portal']
15
- roles_before = api.user.get_roles(TEST_USER_ID)
16
- setRoles(self.portal, TEST_USER_ID, ['Manager'])
17
- self.installer = get_installer(self.portal)
18
- self.installer.uninstall_product('ims.fieldupdater')
19
- setRoles(self.portal, TEST_USER_ID, roles_before)
20
-
21
- def test_product_uninstalled(self):
22
- """Test if ims.contacts is cleanly uninstalled."""
23
- self.assertFalse(self.installer.is_product_installed('ims.fieldupdater'))
@@ -1,17 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: ims.fieldupdater
3
- Version: 3.0.0
4
- Summary: Update all objects in Plone based on a schema/field strategy
5
- Author-email: Eric Wohnlich <wohnlice@imsweb.com>
6
- License: GPL
7
- Project-URL: homepage, https://git.imsweb.com/plone/ims.fieldupdater
8
- Project-URL: documentation, https://git.imsweb.com/plone/ims.fieldupdater
9
- Project-URL: repository, https://git.imsweb.com/plone/ims.fieldupdater
10
- Classifier: Framework :: Plone :: 6.0
11
- Classifier: Programming Language :: Python
12
- Requires-Python: >=3.8
13
- Requires-Dist: plone>=6.0.4
14
- Provides-Extra: test
15
- Requires-Dist: plone.app.testing; extra == "test"
16
- Requires-Dist: plone.mocktestcase; extra == "test"
17
- Requires-Dist: formencode; extra == "test"
@@ -1,26 +0,0 @@
1
- MANIFEST.in
2
- README.md
3
- pyproject.toml
4
- setup.cfg
5
- ims.fieldupdater.egg-info/PKG-INFO
6
- ims.fieldupdater.egg-info/SOURCES.txt
7
- ims.fieldupdater.egg-info/dependency_links.txt
8
- ims.fieldupdater.egg-info/entry_points.txt
9
- ims.fieldupdater.egg-info/requires.txt
10
- ims.fieldupdater.egg-info/top_level.txt
11
- ims/fieldupdater/__init__.py
12
- ims/fieldupdater/configure.zcml
13
- ims/fieldupdater/testing.py
14
- ims/fieldupdater/browser/__init__.py
15
- ims/fieldupdater/browser/configure.zcml
16
- ims/fieldupdater/browser/mass.pt
17
- ims/fieldupdater/browser/mass.py
18
- ims/fieldupdater/browser/schema.pt
19
- ims/fieldupdater/profiles/default/controlpanel.xml
20
- ims/fieldupdater/profiles/default/metadata.xml
21
- ims/fieldupdater/profiles/uninstall/controlpanel.xml
22
- ims/fieldupdater/tests/__init__.py
23
- ims/fieldupdater/tests/base.py
24
- ims/fieldupdater/tests/interfaces.py
25
- ims/fieldupdater/tests/test_mass.py
26
- ims/fieldupdater/tests/test_uninstall.py
@@ -1,2 +0,0 @@
1
- [plone.autoinclude.plugin]
2
- target = plone
@@ -1,6 +0,0 @@
1
- plone>=6.0.4
2
-
3
- [test]
4
- plone.app.testing
5
- plone.mocktestcase
6
- formencode
@@ -1,46 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "ims.fieldupdater"
7
- version = "3.0.0"
8
- requires-python = ">=3.8"
9
- description = "Update all objects in Plone based on a schema/field strategy"
10
- classifiers = [
11
- "Framework :: Plone :: 6.0",
12
- "Programming Language :: Python",
13
- ]
14
- authors = [
15
- { name = "Eric Wohnlich", email = "wohnlice@imsweb.com" }
16
- ]
17
- license = { text = "GPL" }
18
-
19
- dependencies = [
20
- "plone>=6.0.4"
21
- ]
22
-
23
- [project.urls]
24
- homepage = "https://git.imsweb.com/plone/ims.fieldupdater"
25
- documentation = "https://git.imsweb.com/plone/ims.fieldupdater"
26
- repository = "https://git.imsweb.com/plone/ims.fieldupdater"
27
-
28
- [project.optional-dependencies]
29
- test = ["plone.app.testing", "plone.mocktestcase", "formencode"]
30
-
31
- [project.entry-points."plone.autoinclude.plugin"]
32
- target = "plone"
33
-
34
- [tool.bandit]
35
- exclude_dirs = ["*/tests/*"]
36
- skips = ["B313", "B314", "B315", "B316", "B317", "B318", "B319", "B320", "B410", "B404", "B603"]
37
-
38
- [tool.coverage.report]
39
- include = ["ims/fieldupdater/*"]
40
- omit = ["venv", "*/test*", "*upgrades.py"]
41
-
42
- [tool.setuptools.packages.find]
43
- include = ["ims"]
44
-
45
- [tool.flake8]
46
- max-line-length = 120
@@ -1,7 +0,0 @@
1
- [flake8]
2
- max-line-length = 120
3
-
4
- [egg_info]
5
- tag_build =
6
- tag_date = 0
7
-