udata 10.8.1.dev36652__py2.py3-none-any.whl → 10.8.2__py2.py3-none-any.whl

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.

Potentially problematic release.


This version of udata might be problematic. Click here for more details.

Files changed (79) hide show
  1. udata/__init__.py +1 -1
  2. udata/app.py +0 -2
  3. udata/commands/db.py +22 -9
  4. udata/core/dataset/models.py +5 -3
  5. udata/core/discussions/api.py +2 -2
  6. udata/core/jobs/api.py +3 -3
  7. udata/core/metrics/helpers.py +10 -0
  8. udata/core/metrics/tasks.py +144 -1
  9. udata/core/organization/api.py +2 -2
  10. udata/core/post/api.py +1 -1
  11. udata/core/user/api.py +1 -1
  12. udata/features/identicon/api.py +1 -1
  13. udata/harvest/actions.py +24 -28
  14. udata/harvest/api.py +28 -36
  15. udata/harvest/backends/ckan/__init__.py +3 -0
  16. udata/harvest/backends/ckan/harvesters.py +274 -0
  17. udata/harvest/backends/ckan/schemas/__init__.py +0 -0
  18. udata/harvest/backends/ckan/schemas/ckan.py +86 -0
  19. udata/harvest/backends/ckan/schemas/dkan.py +98 -0
  20. udata/harvest/commands.py +7 -7
  21. udata/harvest/tasks.py +1 -1
  22. udata/harvest/tests/ckan/conftest.py +67 -0
  23. udata/harvest/tests/ckan/data/dkan-french-w-license.json +226 -0
  24. udata/harvest/tests/ckan/test_ckan_backend.py +697 -0
  25. udata/harvest/tests/ckan/test_ckan_backend_errors.py +140 -0
  26. udata/harvest/tests/ckan/test_ckan_backend_filters.py +130 -0
  27. udata/harvest/tests/ckan/test_dkan_backend.py +68 -0
  28. udata/harvest/tests/test_actions.py +27 -32
  29. udata/harvest/tests/test_api.py +23 -18
  30. udata/harvest/tests/test_dcat_backend.py +29 -29
  31. udata/migrations/2025-07-30-purge-old-harvest-dynamic-fields.py +29 -0
  32. udata/mongo/slug_fields.py +25 -8
  33. udata/routing.py +6 -0
  34. udata/static/chunks/{11.b6f741fcc366abfad9c4.js → 11.51d706fb9521c16976bc.js} +3 -3
  35. udata/static/chunks/{11.b6f741fcc366abfad9c4.js.map → 11.51d706fb9521c16976bc.js.map} +1 -1
  36. udata/static/chunks/{13.2d06442dd9a05d9777b5.js → 13.39e106d56f794ebd06a0.js} +2 -2
  37. udata/static/chunks/{13.2d06442dd9a05d9777b5.js.map → 13.39e106d56f794ebd06a0.js.map} +1 -1
  38. udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js → 17.70cbb4a91b002338007e.js} +2 -2
  39. udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js.map → 17.70cbb4a91b002338007e.js.map} +1 -1
  40. udata/static/chunks/{19.f03a102365af4315f9db.js → 19.a348a5fff8fe2801e52a.js} +3 -3
  41. udata/static/chunks/{19.f03a102365af4315f9db.js.map → 19.a348a5fff8fe2801e52a.js.map} +1 -1
  42. udata/static/chunks/{5.0fa1408dae4e76b87b2e.js → 5.343ca020a2d38cec1a14.js} +3 -3
  43. udata/static/chunks/{5.0fa1408dae4e76b87b2e.js.map → 5.343ca020a2d38cec1a14.js.map} +1 -1
  44. udata/static/chunks/{6.d663709d877baa44a71e.js → 6.a3b07de9dd2ca2d24e85.js} +3 -3
  45. udata/static/chunks/{6.d663709d877baa44a71e.js.map → 6.a3b07de9dd2ca2d24e85.js.map} +1 -1
  46. udata/static/chunks/{8.778091d55cd8ea39af6b.js → 8.462bb3029de008497675.js} +2 -2
  47. udata/static/chunks/{8.778091d55cd8ea39af6b.js.map → 8.462bb3029de008497675.js.map} +1 -1
  48. udata/static/common.js +1 -1
  49. udata/static/common.js.map +1 -1
  50. udata/tests/api/test_datasets_api.py +0 -46
  51. udata/tests/api/test_organizations_api.py +5 -0
  52. udata/tests/cli/test_db_cli.py +12 -0
  53. udata/tests/dataset/test_dataset_model.py +0 -16
  54. udata/tests/metrics/__init__.py +0 -0
  55. udata/tests/metrics/conftest.py +15 -0
  56. udata/tests/metrics/helpers.py +58 -0
  57. udata/tests/metrics/test_metrics.py +67 -0
  58. udata/tests/metrics/test_tasks.py +171 -0
  59. udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
  60. udata/translations/ar/LC_MESSAGES/udata.po +72 -65
  61. udata/translations/de/LC_MESSAGES/udata.mo +0 -0
  62. udata/translations/de/LC_MESSAGES/udata.po +72 -65
  63. udata/translations/es/LC_MESSAGES/udata.mo +0 -0
  64. udata/translations/es/LC_MESSAGES/udata.po +72 -65
  65. udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
  66. udata/translations/fr/LC_MESSAGES/udata.po +72 -65
  67. udata/translations/it/LC_MESSAGES/udata.mo +0 -0
  68. udata/translations/it/LC_MESSAGES/udata.po +72 -65
  69. udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
  70. udata/translations/pt/LC_MESSAGES/udata.po +72 -65
  71. udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
  72. udata/translations/sr/LC_MESSAGES/udata.po +72 -65
  73. udata/translations/udata.pot +74 -70
  74. {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/METADATA +16 -2
  75. {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/RECORD +79 -62
  76. {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/entry_points.txt +2 -0
  77. {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/LICENSE +0 -0
  78. {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/WHEEL +0 -0
  79. {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,140 @@
1
+ import pytest
2
+
3
+ from udata.harvest import actions
4
+ from udata.harvest.tests.factories import HarvestSourceFactory
5
+
6
+ pytestmark = [
7
+ pytest.mark.usefixtures("clean_db"),
8
+ pytest.mark.options(PLUGINS=["ckan"]),
9
+ ]
10
+
11
+ CKAN_URL = "https://harvest.me/"
12
+ API_URL = "{}api/3/action/package_list".format(CKAN_URL)
13
+
14
+ # We test against success and error status code
15
+ # because CKAN API always return 200
16
+ # but some other cases may happen outside the API
17
+ STATUS_CODE = (400, 500)
18
+
19
+
20
+ @pytest.mark.parametrize("code", STATUS_CODE)
21
+ def test_html_error(rmock, code):
22
+ # Happens with wrong source URL (html is returned instead of json)
23
+ html = "<html><body>Error</body></html>"
24
+ source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
25
+
26
+ rmock.get(API_URL, text=html, status_code=code, headers={"Content-Type": "text/html"})
27
+
28
+ actions.run(source)
29
+
30
+ source.reload()
31
+
32
+ job = source.get_last_job()
33
+ assert len(job.items) == 0
34
+ assert len(job.errors) == 1
35
+ error = job.errors[0]
36
+ # HTML is detected and does not clutter the message
37
+ assert html not in error.message
38
+
39
+
40
+ @pytest.mark.parametrize("code", STATUS_CODE)
41
+ def test_plain_text_error(rmock, code):
42
+ source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
43
+
44
+ rmock.get(
45
+ API_URL, text='"Some error"', status_code=code, headers={"Content-Type": "text/plain"}
46
+ )
47
+
48
+ actions.run(source)
49
+
50
+ source.reload()
51
+
52
+ job = source.get_last_job()
53
+ assert len(job.items) == 0
54
+ assert len(job.errors) == 1
55
+ error = job.errors[0]
56
+ # Raw quoted string is properly unquoted
57
+ http_message = "Server Error" if code == 500 else "Client Error"
58
+ assert (
59
+ error.message
60
+ == f"{code} {http_message}: None for url: https://harvest.me/api/3/action/package_list"
61
+ )
62
+
63
+
64
+ def test_200_plain_text_error(rmock):
65
+ source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
66
+
67
+ rmock.get(API_URL, text='"Some error"', status_code=200, headers={"Content-Type": "text/plain"})
68
+
69
+ actions.run(source)
70
+
71
+ source.reload()
72
+
73
+ job = source.get_last_job()
74
+ assert len(job.items) == 0
75
+ assert len(job.errors) == 1
76
+ error = job.errors[0]
77
+ # Raw quoted string is properly unquoted
78
+ assert error.message == "Some error"
79
+
80
+
81
+ def test_standard_api_json_error(rmock):
82
+ json = {"success": False, "error": "an error"}
83
+ source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
84
+
85
+ rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
86
+
87
+ actions.run(source)
88
+
89
+ source.reload()
90
+
91
+ job = source.get_last_job()
92
+ assert len(job.items) == 0
93
+ assert len(job.errors) == 1
94
+ error = job.errors[0]
95
+ assert error.message == "an error"
96
+
97
+
98
+ def test_standard_api_json_error_with_details(rmock):
99
+ json = {
100
+ "success": False,
101
+ "error": {
102
+ "message": "an error",
103
+ },
104
+ }
105
+ source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
106
+
107
+ rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
108
+
109
+ actions.run(source)
110
+
111
+ source.reload()
112
+
113
+ job = source.get_last_job()
114
+ assert len(job.items) == 0
115
+ assert len(job.errors) == 1
116
+ error = job.errors[0]
117
+ assert error.message == "an error"
118
+
119
+
120
+ def test_standard_api_json_error_with_details_and_type(rmock):
121
+ json = {
122
+ "success": False,
123
+ "error": {
124
+ "message": "Access denied",
125
+ "__type": "Authorization Error",
126
+ },
127
+ }
128
+ source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
129
+
130
+ rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
131
+
132
+ actions.run(source)
133
+
134
+ source.reload()
135
+
136
+ job = source.get_last_job()
137
+ assert len(job.items) == 0
138
+ assert len(job.errors) == 1
139
+ error = job.errors[0]
140
+ assert error.message == "Authorization Error: Access denied"
@@ -0,0 +1,130 @@
1
+ import urllib
2
+
3
+ import pytest
4
+
5
+ from udata.harvest import actions
6
+ from udata.harvest.tests.factories import HarvestSourceFactory
7
+ from udata.utils import faker
8
+
9
+ pytestmark = [
10
+ pytest.mark.usefixtures("clean_db"),
11
+ pytest.mark.options(PLUGINS=["ckan"]),
12
+ ]
13
+
14
+
15
+ def test_include_org_filter(ckan, rmock):
16
+ source = HarvestSourceFactory(
17
+ backend="ckan",
18
+ url=ckan.BASE_URL,
19
+ config={"filters": [{"key": "organization", "value": "organization_name"}]},
20
+ )
21
+
22
+ rmock.get(
23
+ ckan.PACKAGE_SEARCH_URL,
24
+ json={"success": True, "result": {"results": []}},
25
+ status_code=200,
26
+ headers={"Content-Type": "application/json"},
27
+ )
28
+
29
+ actions.run(source)
30
+ source.reload()
31
+
32
+ assert rmock.call_count == 1
33
+ params = {"q": "organization:organization_name", "rows": 1000}
34
+ assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
35
+
36
+
37
+ def test_exclude_org_filter(ckan, rmock):
38
+ source = HarvestSourceFactory(
39
+ backend="ckan",
40
+ url=ckan.BASE_URL,
41
+ config={
42
+ "filters": [{"key": "organization", "value": "organization_name", "type": "exclude"}]
43
+ },
44
+ )
45
+
46
+ rmock.get(
47
+ ckan.PACKAGE_SEARCH_URL,
48
+ json={"success": True, "result": {"results": []}},
49
+ status_code=200,
50
+ headers={"Content-Type": "application/json"},
51
+ )
52
+
53
+ actions.run(source)
54
+ source.reload()
55
+
56
+ assert rmock.call_count == 1
57
+
58
+ params = {"q": "-organization:organization_name", "rows": 1000}
59
+ assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
60
+
61
+
62
+ def test_tag_filter(ckan, rmock):
63
+ tag = faker.word()
64
+ source = HarvestSourceFactory(
65
+ backend="ckan", url=ckan.BASE_URL, config={"filters": [{"key": "tags", "value": tag}]}
66
+ )
67
+
68
+ rmock.get(
69
+ ckan.PACKAGE_SEARCH_URL,
70
+ json={"success": True, "result": {"results": []}},
71
+ status_code=200,
72
+ headers={"Content-Type": "application/json"},
73
+ )
74
+
75
+ actions.run(source)
76
+ source.reload()
77
+
78
+ assert rmock.call_count == 1
79
+ params = {"q": f"tags:{tag}", "rows": 1000}
80
+ assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
81
+
82
+
83
+ def test_exclude_tag_filter(ckan, rmock):
84
+ tag = faker.word()
85
+ source = HarvestSourceFactory(
86
+ backend="ckan",
87
+ url=ckan.BASE_URL,
88
+ config={"filters": [{"key": "tags", "value": tag, "type": "exclude"}]},
89
+ )
90
+
91
+ rmock.get(
92
+ ckan.PACKAGE_SEARCH_URL,
93
+ json={"success": True, "result": {"results": []}},
94
+ status_code=200,
95
+ headers={"Content-Type": "application/json"},
96
+ )
97
+
98
+ actions.run(source)
99
+ source.reload()
100
+
101
+ assert rmock.call_count == 1
102
+ params = {"q": f"-tags:{tag}", "rows": 1000}
103
+ assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
104
+
105
+
106
+ def test_can_have_multiple_filters(ckan, rmock):
107
+ source = HarvestSourceFactory(
108
+ backend="ckan",
109
+ url=ckan.BASE_URL,
110
+ config={
111
+ "filters": [
112
+ {"key": "organization", "value": "organization_name"},
113
+ {"key": "tags", "value": "tag-2", "type": "exclude"},
114
+ ]
115
+ },
116
+ )
117
+
118
+ rmock.get(
119
+ ckan.PACKAGE_SEARCH_URL,
120
+ json={"success": True, "result": {"results": []}},
121
+ status_code=200,
122
+ headers={"Content-Type": "application/json"},
123
+ )
124
+
125
+ actions.run(source)
126
+ source.reload()
127
+
128
+ assert rmock.call_count == 1
129
+ params = {"q": "organization:organization_name AND -tags:tag-2", "rows": 1000}
130
+ assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
@@ -0,0 +1,68 @@
1
+ import json
2
+ import os
3
+ from datetime import datetime
4
+
5
+ import pytest
6
+
7
+ from udata.app import create_app
8
+ from udata.core.organization.factories import OrganizationFactory
9
+ from udata.harvest import actions
10
+ from udata.harvest.tests.factories import HarvestSourceFactory
11
+ from udata.models import Dataset
12
+ from udata.settings import Defaults, Testing
13
+ from udata.tests.plugin import drop_db
14
+
15
+
16
+ def data_path(filename):
17
+ """Get a test data path"""
18
+ return os.path.join(os.path.dirname(__file__), "data", filename)
19
+
20
+
21
+ class DkanSettings(Testing):
22
+ PLUGINS = ["dkan"]
23
+
24
+
25
+ @pytest.fixture(scope="module")
26
+ def app(request):
27
+ """Create an udata app once for the module."""
28
+ app = create_app(Defaults, override=DkanSettings)
29
+ with app.app_context():
30
+ drop_db(app)
31
+ yield app
32
+ with app.app_context():
33
+ drop_db(app)
34
+
35
+
36
+ def test_dkan_french_w_license(app, rmock):
37
+ """CKAN Harvester should accept the minimum dataset payload"""
38
+ DKAN_URL = "https://harvest.me/"
39
+ API_URL = "{}api/3/action/".format(DKAN_URL)
40
+ PACKAGE_LIST_URL = "{}package_list".format(API_URL)
41
+ PACKAGE_SHOW_URL = "{}package_show".format(API_URL)
42
+
43
+ with open(data_path("dkan-french-w-license.json")) as ifile:
44
+ data = json.loads(ifile.read())
45
+
46
+ org = OrganizationFactory()
47
+ source = HarvestSourceFactory(backend="dkan", url=DKAN_URL, organization=org)
48
+ rmock.get(
49
+ PACKAGE_LIST_URL,
50
+ json={"success": True, "result": ["fake-name"]},
51
+ status_code=200,
52
+ headers={"Content-Type": "application/json"},
53
+ )
54
+ rmock.get(
55
+ PACKAGE_SHOW_URL, json=data, status_code=200, headers={"Content-Type": "application/json"}
56
+ )
57
+ actions.run(source)
58
+ source.reload()
59
+ assert source.get_last_job().status == "done"
60
+
61
+ datasets = Dataset.objects.filter(organization=org)
62
+ assert len(datasets) > 0
63
+
64
+ dataset = datasets.get(**{"harvest__remote_id": "04be6288-696d-4331-850d-a144871a7e3a"})
65
+ assert dataset.harvest.created_at == datetime(2019, 12, 10, 0, 0)
66
+ assert dataset.harvest.modified_at == datetime(2019, 9, 30, 0, 0)
67
+ assert len(dataset.resources) == 2
68
+ assert "xlsx" in [r.format for r in dataset.resources]
@@ -152,7 +152,7 @@ class HarvestActionsTest:
152
152
  data["url"] = new_url
153
153
 
154
154
  with assert_emit(signals.harvest_source_updated):
155
- source = actions.update_source(source.id, data)
155
+ source = actions.update_source(source, data)
156
156
 
157
157
  assert source.url == new_url
158
158
  source.reload()
@@ -162,14 +162,14 @@ class HarvestActionsTest:
162
162
  def test_validate_source(self, mock):
163
163
  source = HarvestSourceFactory()
164
164
 
165
- actions.validate_source(source.id)
165
+ actions.validate_source(source)
166
166
 
167
167
  source.reload()
168
168
  assert source.validation.state == VALIDATION_ACCEPTED
169
169
  assert source.validation.on is not None
170
170
  assert source.validation.by is None
171
171
  assert source.validation.comment is None
172
- mock.assert_called_once_with(source.id)
172
+ mock.assert_called_once_with(source)
173
173
 
174
174
  assert source.periodic_task is not None
175
175
 
@@ -177,7 +177,7 @@ class HarvestActionsTest:
177
177
  def test_validate_source_with_comment(self, mock):
178
178
  source = HarvestSourceFactory()
179
179
 
180
- actions.validate_source(source.id, "comment")
180
+ actions.validate_source(source, "comment")
181
181
 
182
182
  source.reload()
183
183
 
@@ -185,12 +185,12 @@ class HarvestActionsTest:
185
185
  assert source.validation.on is not None
186
186
  assert source.validation.by is None
187
187
  assert source.validation.comment == "comment"
188
- mock.assert_called_once_with(source.id)
188
+ mock.assert_called_once_with(source)
189
189
 
190
190
  def test_reject_source(self):
191
191
  source = HarvestSourceFactory()
192
192
 
193
- actions.reject_source(source.id, "comment")
193
+ actions.reject_source(source, "comment")
194
194
 
195
195
  source.reload()
196
196
  assert source.validation.state == VALIDATION_REFUSED
@@ -200,20 +200,16 @@ class HarvestActionsTest:
200
200
 
201
201
  def test_get_source_by_slug(self):
202
202
  source = HarvestSourceFactory()
203
- assert actions.get_source(source.slug) == source
203
+ assert HarvestSource.get(source.slug) == source
204
204
 
205
205
  def test_get_source_by_id(self):
206
206
  source = HarvestSourceFactory()
207
- assert actions.get_source(str(source.id)) == source
208
-
209
- def test_get_source_by_objectid(self):
210
- source = HarvestSourceFactory()
211
- assert actions.get_source(source.id) == source
207
+ assert HarvestSource.get(str(source.id)) == source
212
208
 
213
209
  def test_delete_source_by_slug(self):
214
210
  source = HarvestSourceFactory()
215
211
  with assert_emit(signals.harvest_source_deleted):
216
- deleted_source = actions.delete_source(source.slug)
212
+ deleted_source = actions.delete_source(source)
217
213
 
218
214
  assert deleted_source.deleted is not None
219
215
  assert deleted_source.id == source.id
@@ -223,7 +219,7 @@ class HarvestActionsTest:
223
219
  def test_delete_source_by_id(self):
224
220
  source = HarvestSourceFactory()
225
221
  with assert_emit(signals.harvest_source_deleted):
226
- deleted_source = actions.delete_source(str(source.id))
222
+ deleted_source = actions.delete_source(source)
227
223
 
228
224
  assert deleted_source.deleted is not None
229
225
  assert deleted_source.id == source.id
@@ -233,19 +229,18 @@ class HarvestActionsTest:
233
229
  def test_delete_source_by_objectid(self):
234
230
  source = HarvestSourceFactory()
235
231
  with assert_emit(signals.harvest_source_deleted):
236
- deleted_source = actions.delete_source(source.id)
232
+ deleted_source = actions.delete_source(source)
237
233
 
238
234
  assert deleted_source.deleted is not None
239
235
  assert deleted_source.id == source.id
240
236
  deleted_sources = HarvestSource.objects(deleted__exists=True)
241
237
  assert len(deleted_sources) == 1
242
238
 
243
- @pytest.mark.parametrize("by_attr", ["source.id", "str(source.id)", "source.slug"])
244
- def test_clean_source(self, by_attr):
239
+ def test_clean_source(self):
245
240
  source = HarvestSourceFactory()
246
241
  for _ in range(5):
247
242
  DatasetFactory(harvest=HarvestDatasetMetadata(source_id=str(source.id)))
248
- actions.clean_source(eval(by_attr))
243
+ actions.clean_source(source)
249
244
  datasets = Dataset.objects.filter(harvest__source_id=str(source.id))
250
245
  assert len(datasets) == 5
251
246
  for dataset in datasets:
@@ -262,7 +257,7 @@ class HarvestActionsTest:
262
257
  def test_schedule(self):
263
258
  source = HarvestSourceFactory()
264
259
  with assert_emit(signals.harvest_source_scheduled):
265
- source = actions.schedule(str(source.id), hour=0)
260
+ source = actions.schedule(source, hour=0)
266
261
 
267
262
  assert len(PeriodicTask.objects) == 1
268
263
  periodic_task = source.periodic_task
@@ -279,7 +274,7 @@ class HarvestActionsTest:
279
274
  def test_schedule_from_cron(self):
280
275
  source = HarvestSourceFactory()
281
276
  with assert_emit(signals.harvest_source_scheduled):
282
- source = actions.schedule(str(source.id), "0 1 2 3 sunday")
277
+ source = actions.schedule(source, "0 1 2 3 sunday")
283
278
 
284
279
  assert len(PeriodicTask.objects) == 1
285
280
  periodic_task = source.periodic_task
@@ -296,10 +291,10 @@ class HarvestActionsTest:
296
291
  def test_reschedule(self):
297
292
  source = HarvestSourceFactory()
298
293
  with assert_emit(signals.harvest_source_scheduled):
299
- source = actions.schedule(str(source.id), hour=0)
294
+ source = actions.schedule(source, hour=0)
300
295
 
301
296
  with assert_emit(signals.harvest_source_scheduled):
302
- source = actions.schedule(str(source.id), minute=0)
297
+ source = actions.schedule(source, minute=0)
303
298
 
304
299
  assert len(PeriodicTask.objects) == 1
305
300
  periodic_task = source.periodic_task
@@ -323,7 +318,7 @@ class HarvestActionsTest:
323
318
  )
324
319
  source = HarvestSourceFactory(periodic_task=periodic_task)
325
320
  with assert_emit(signals.harvest_source_unscheduled):
326
- actions.unschedule(str(source.id))
321
+ actions.unschedule(source)
327
322
 
328
323
  source.reload()
329
324
  assert len(PeriodicTask.objects) == 0
@@ -460,7 +455,7 @@ class ExecutionTestMixin(MockBackendsMixin):
460
455
  org = OrganizationFactory()
461
456
  source = HarvestSourceFactory(backend="factory", organization=org)
462
457
  with assert_emit(signals.before_harvest_job, signals.after_harvest_job):
463
- self.action(source.slug)
458
+ self.action(source)
464
459
 
465
460
  source.reload()
466
461
  assert len(HarvestJob.objects(source=source)) == 1
@@ -496,7 +491,7 @@ class ExecutionTestMixin(MockBackendsMixin):
496
491
 
497
492
  source = HarvestSourceFactory(backend="factory")
498
493
  with assert_emit(signals.before_harvest_job), mock_initialize.connected_to(init):
499
- self.action(source.slug)
494
+ self.action(source)
500
495
 
501
496
  source.reload()
502
497
  assert len(HarvestJob.objects(source=source)) == 1
@@ -523,7 +518,7 @@ class ExecutionTestMixin(MockBackendsMixin):
523
518
  assert_emit(signals.before_harvest_job, signals.after_harvest_job),
524
519
  mock_process.connected_to(process),
525
520
  ):
526
- self.action(source.slug)
521
+ self.action(source)
527
522
 
528
523
  source.reload()
529
524
  assert len(HarvestJob.objects(source=source)) == 1
@@ -559,7 +554,7 @@ class ExecutionTestMixin(MockBackendsMixin):
559
554
  def test_empty(self):
560
555
  source = HarvestSourceFactory(backend="factory", config={"count": 0})
561
556
  with assert_emit(signals.before_harvest_job, signals.after_harvest_job):
562
- self.action(source.slug)
557
+ self.action(source)
563
558
 
564
559
  source.reload()
565
560
  assert len(HarvestJob.objects(source=source)) == 1
@@ -580,7 +575,7 @@ class ExecutionTestMixin(MockBackendsMixin):
580
575
  org = OrganizationFactory()
581
576
  source = HarvestSourceFactory(backend="factory", organization=org, config={"count": 10})
582
577
 
583
- self.action(source.slug)
578
+ self.action(source)
584
579
  assert len(Dataset.objects) == 5
585
580
 
586
581
 
@@ -599,7 +594,7 @@ class HarvestPreviewTest(MockBackendsMixin):
599
594
  org = OrganizationFactory()
600
595
  source = HarvestSourceFactory(backend="factory", organization=org)
601
596
 
602
- job = actions.preview(source.slug)
597
+ job = actions.preview(source)
603
598
 
604
599
  assert job.status == "done"
605
600
  assert job.errors == []
@@ -628,7 +623,7 @@ class HarvestPreviewTest(MockBackendsMixin):
628
623
  org = OrganizationFactory()
629
624
  source = HarvestSourceFactory(backend="factory", organization=org, config={"count": 10})
630
625
 
631
- job = actions.preview(source.slug)
626
+ job = actions.preview(source)
632
627
 
633
628
  assert len(job.items) == 5
634
629
 
@@ -639,7 +634,7 @@ class HarvestPreviewTest(MockBackendsMixin):
639
634
  source = HarvestSourceFactory(backend="factory")
640
635
 
641
636
  with mock_initialize.connected_to(init):
642
- job = actions.preview(source.slug)
637
+ job = actions.preview(source)
643
638
 
644
639
  assert job.status == "failed"
645
640
  assert len(job.errors) == 1
@@ -660,7 +655,7 @@ class HarvestPreviewTest(MockBackendsMixin):
660
655
  source = HarvestSourceFactory(backend="factory")
661
656
 
662
657
  with mock_process.connected_to(process):
663
- job = actions.preview(source.slug)
658
+ job = actions.preview(source)
664
659
 
665
660
  assert job.status == "done-errors"
666
661
  assert job.started is not None