ckanext-csvwmapandtransform 0.0.1__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.
Files changed (34) hide show
  1. ckanext/csvwmapandtransform/__init__.py +22 -0
  2. ckanext/csvwmapandtransform/action.py +405 -0
  3. ckanext/csvwmapandtransform/assets/.gitignore +0 -0
  4. ckanext/csvwmapandtransform/assets/script.js +81 -0
  5. ckanext/csvwmapandtransform/assets/style.css +124 -0
  6. ckanext/csvwmapandtransform/assets/webassets.yml +13 -0
  7. ckanext/csvwmapandtransform/auth.py +23 -0
  8. ckanext/csvwmapandtransform/cli.py +18 -0
  9. ckanext/csvwmapandtransform/db.py +397 -0
  10. ckanext/csvwmapandtransform/helpers.py +67 -0
  11. ckanext/csvwmapandtransform/i18n/.gitignore +0 -0
  12. ckanext/csvwmapandtransform/i18n/ckanext-csvwmapandtransform.pot +108 -0
  13. ckanext/csvwmapandtransform/i18n/de/LC_MESSAGES/ckanext-csvwmapandtransform.mo +0 -0
  14. ckanext/csvwmapandtransform/i18n/de/LC_MESSAGES/ckanext-csvwmapandtransform.po +113 -0
  15. ckanext/csvwmapandtransform/mapper.py +133 -0
  16. ckanext/csvwmapandtransform/plugin.py +140 -0
  17. ckanext/csvwmapandtransform/public/.gitignore +0 -0
  18. ckanext/csvwmapandtransform/public/dotted.png +0 -0
  19. ckanext/csvwmapandtransform/tasks.py +262 -0
  20. ckanext/csvwmapandtransform/templates/.gitignore +0 -0
  21. ckanext/csvwmapandtransform/templates/csvwmapandtransform/create_mapping.html +56 -0
  22. ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html +108 -0
  23. ckanext/csvwmapandtransform/templates/package/resource_read.html +8 -0
  24. ckanext/csvwmapandtransform/templates/package/snippets/resource_item.html +23 -0
  25. ckanext/csvwmapandtransform/tests/__init__.py +0 -0
  26. ckanext/csvwmapandtransform/views.py +205 -0
  27. ckanext_csvwmapandtransform-0.0.1-py3.14-nspkg.pth +1 -0
  28. ckanext_csvwmapandtransform-0.0.1.dist-info/METADATA +121 -0
  29. ckanext_csvwmapandtransform-0.0.1.dist-info/RECORD +34 -0
  30. ckanext_csvwmapandtransform-0.0.1.dist-info/WHEEL +5 -0
  31. ckanext_csvwmapandtransform-0.0.1.dist-info/entry_points.txt +5 -0
  32. ckanext_csvwmapandtransform-0.0.1.dist-info/licenses/LICENSE +661 -0
  33. ckanext_csvwmapandtransform-0.0.1.dist-info/namespace_packages.txt +1 -0
  34. ckanext_csvwmapandtransform-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ """
3
+ ckanext-csvwmapandtransform
4
+
5
+ A CKAN extension to automate mapping of CSVW metadata documents to knowledge graphs
6
+ and run automatic pipelines.
7
+ """
8
+
9
+ # Expose package version from setuptools-scm
10
+ try:
11
+ from importlib.metadata import version, PackageNotFoundError
12
+ except ImportError:
13
+ # Python < 3.8 fallback
14
+ from importlib_metadata import version, PackageNotFoundError
15
+
16
+ try:
17
+ __version__ = version("ckanext-csvwmapandtransform")
18
+ except PackageNotFoundError:
19
+ # Package is not installed
20
+ __version__ = "unknown"
21
+
22
+ __all__ = ["__version__"]
@@ -0,0 +1,405 @@
1
+ import ckan.plugins.toolkit as toolkit
2
+
3
+ if toolkit.check_ckan_version("2.10"):
4
+ from ckan.types import Context
5
+ else:
6
+
7
+ class Context(dict):
8
+ def __init__(self, **kwargs):
9
+ super().__init__(**kwargs)
10
+
11
+
12
+ import datetime
13
+ import itertools
14
+ import json
15
+ import os
16
+ from typing import Any
17
+
18
+ import ckanapi
19
+ import sqlalchemy as sa
20
+ from ckan import model
21
+ from ckan.lib.jobs import DEFAULT_QUEUE_NAME
22
+ from dateutil.parser import isoparse as parse_iso_date
23
+ from dateutil.parser import parse as parse_date
24
+
25
+ from ckanext.csvwmapandtransform import db, mapper
26
+ from ckanext.csvwmapandtransform.tasks import transform
27
+
28
+ log = __import__("logging").getLogger(__name__)
29
+ # must be lower case alphanumeric and these symbols: -_
30
+ MAPPING_GROUP = "mappings"
31
+ METHOD_GROUP = "methods"
32
+ JOB_TIMEOUT = 180
33
+
34
+
35
+ def find_first_matching_id(dicts: list, key: str, value: str):
36
+ return next((d["id"] for d in dicts if d.get(key) == value), None)
37
+
38
+
39
+ def csvwmapandtransform_find_mappings(context: Context, data_dict):
40
+ mapping_group_id = find_first_matching_id(
41
+ toolkit.get_action("group_list")({}, {"all_fields": True}),
42
+ key="name",
43
+ value=MAPPING_GROUP,
44
+ )
45
+ if mapping_group_id:
46
+ mapping_group = toolkit.get_action("group_show")(
47
+ {"ignore_auth": True}, {"id": mapping_group_id, "include_datasets": True}
48
+ )
49
+ else:
50
+ log.warn("group with name mappings not found!")
51
+ mapping_group = create_group(MAPPING_GROUP)
52
+ log.info("created group mappings")
53
+ packages = mapping_group.get("packages", None)
54
+
55
+ if packages:
56
+ packages = [
57
+ toolkit.get_action("package_show")({}, {"id": package["id"]})
58
+ for package in packages
59
+ ]
60
+ resources = list(
61
+ itertools.chain.from_iterable(
62
+ [package["resources"] for package in packages]
63
+ )
64
+ )
65
+ else:
66
+ resources = list()
67
+ return resources
68
+
69
+
70
+ def csvwmapandtransform_test_mappings(context: Context, data_dict):
71
+ data_url = data_dict.get("data_url", None)
72
+ map_urls = data_dict.get("map_urls", None)
73
+ if not map_urls:
74
+ msg = {"map_urls": ["this field is mandatory."]}
75
+ raise toolkit.ValidationError(msg)
76
+ elif not data_url:
77
+ msg = {"data_url": ["this field is mandatory."]}
78
+ raise toolkit.ValidationError(msg)
79
+ tests = [mapper.check_mapping(map_url=url, data_url=data_url) for url in map_urls]
80
+ return tests
81
+
82
+
83
+ def csvwmapandtransform_transform(
84
+ context: Context, data_dict: dict[str, Any]
85
+ ) -> dict[str, Any]:
86
+ """Start a the transformation job for a certain resource.
87
+
88
+ :param resource_id: The resource id of the resource that you want the
89
+ datapusher status for.
90
+ :type resource_id: string
91
+ """
92
+ if "id" in data_dict:
93
+ data_dict["resource_id"] = data_dict["id"]
94
+ res_id = toolkit.get_or_bust(data_dict, "resource_id")
95
+ resource = toolkit.get_action("resource_show")(
96
+ {"ignore_auth": True}, {"id": res_id}
97
+ )
98
+ data_dict["resource"] = resource
99
+ toolkit.check_access("csvwmapandtransform_transform", context, data_dict)
100
+
101
+ log.debug("transform_started for: {}".format(resource))
102
+ res = enqueue_transform(
103
+ resource["id"],
104
+ resource["name"],
105
+ resource["url"],
106
+ resource["package_id"],
107
+ operation="changed",
108
+ )
109
+ return res
110
+
111
+
112
+ @toolkit.side_effect_free
113
+ def csvwmapandtransform_transform_status(
114
+ context: Context, data_dict: dict[str, Any]
115
+ ) -> dict[str, Any]:
116
+ """Get the status of a the transformation job for a certain resource.
117
+
118
+ :param resource_id: The resource id of the resource that you want the
119
+ datapusher status for.
120
+ :type resource_id: string
121
+ """
122
+ toolkit.check_access("csvwmapandtransform_transform_status", context, data_dict)
123
+
124
+ if "id" in data_dict:
125
+ data_dict["resource_id"] = data_dict["id"]
126
+ res_id = toolkit.get_or_bust(data_dict, "resource_id")
127
+ job_id = None
128
+
129
+ try:
130
+ task = toolkit.get_action("task_status_show")(
131
+ {},
132
+ {
133
+ "entity_id": res_id,
134
+ "task_type": "csvwmapandtransform",
135
+ "key": "csvwmapandtransform",
136
+ },
137
+ )
138
+ except:
139
+ status = None
140
+ else:
141
+ value = json.loads(task["value"])
142
+ job_id = value.get("job_id")
143
+ url = None
144
+ job_detail = None
145
+ try:
146
+ error = json.loads(task["error"])
147
+ except ValueError:
148
+ # this happens occasionally, such as when the job times out
149
+ error = task["error"]
150
+ status = {
151
+ "status": task["state"],
152
+ "job_id": job_id,
153
+ "job_url": url,
154
+ "last_updated": task["last_updated"],
155
+ "error": error,
156
+ }
157
+ if job_id:
158
+ # get logs from db
159
+ db.init()
160
+ db_job = db.get_job(job_id)
161
+
162
+ if db_job and db_job.get("logs"):
163
+ for log in db_job["logs"]:
164
+ if "timestamp" in log and isinstance(
165
+ log["timestamp"], datetime.datetime
166
+ ):
167
+ log["timestamp"] = log["timestamp"].isoformat()
168
+ status = dict(status, **db_job)
169
+ # status['task_info']=db_job
170
+ return status
171
+
172
+
173
+ def get_actions():
174
+ actions = {
175
+ "csvwmapandtransform_find_mappings": csvwmapandtransform_find_mappings,
176
+ "csvwmapandtransform_transform": csvwmapandtransform_transform,
177
+ "csvwmapandtransform_test_mappings": csvwmapandtransform_test_mappings,
178
+ "csvwmapandtransform_transform_status": csvwmapandtransform_transform_status,
179
+ "csvwmapandtransform_hook": csvwmapandtransform_hook,
180
+ }
181
+ return actions
182
+
183
+
184
+ def create_group(name):
185
+ local_ckan = ckanapi.LocalCKAN()
186
+ group = local_ckan.action.group_create(name=name)
187
+ return group
188
+
189
+
190
+ def enqueue_transform(res_id, res_name, res_url, dataset_id, operation):
191
+ # skip task if the dataset is already queued
192
+ queue = DEFAULT_QUEUE_NAME
193
+ # jobs = toolkit.get_action("job_list")({"ignore_auth": True}, {"queues": [queue]})
194
+ # log.debug("test-jobs")
195
+ # log.debug(jobs)
196
+ # Check if this resource is already in the process of being xloadered
197
+ task = {
198
+ "entity_id": res_id,
199
+ "entity_type": "resource",
200
+ "task_type": "csvwmapandtransform",
201
+ "last_updated": str(datetime.datetime.utcnow()),
202
+ "state": "submitting",
203
+ "key": "csvwmapandtransform",
204
+ "value": "{}",
205
+ "error": "{}",
206
+ "detail": "",
207
+ }
208
+ try:
209
+ existing_task = toolkit.get_action("task_status_show")(
210
+ {},
211
+ {
212
+ "entity_id": res_id,
213
+ "task_type": "csvwmapandtransform",
214
+ "key": "csvwmapandtransform",
215
+ },
216
+ )
217
+ assume_task_stale_after = datetime.timedelta(seconds=3600)
218
+ assume_task_stillborn_after = datetime.timedelta(seconds=int(5))
219
+ if existing_task.get("state") == "pending":
220
+ # queued_res_ids = [
221
+ # re.search(r"'resource_id': u?'([^']+)'",
222
+ # job.description).groups()[0]
223
+ # for job in get_queue().get_jobs()
224
+ # if 'xloader_to_datastore' in str(job) # filter out test_job etc
225
+ # ]
226
+ updated = parse_iso_date(existing_task["last_updated"])
227
+ time_since_last_updated = datetime.datetime.utcnow() - updated
228
+ # if (res_id not in queued_res_ids
229
+ # and time_since_last_updated > assume_task_stillborn_after):
230
+ # # it's not on the queue (and if it had just been started then
231
+ # # its taken too long to update the task_status from pending -
232
+ # # the first thing it should do in the xloader job).
233
+ # # Let it be restarted.
234
+ # log.info('A pending task was found %r, but its not found in '
235
+ # 'the queue %r and is %s hours old',
236
+ # existing_task['id'], queued_res_ids,
237
+ # time_since_last_updated)
238
+ # elif time_since_last_updated > assume_task_stale_after:
239
+ if time_since_last_updated > assume_task_stale_after:
240
+ # it's been a while since the job was last updated - it's more
241
+ # likely something went wrong with it and the state wasn't
242
+ # updated than its still in progress. Let it be restarted.
243
+ log.info(
244
+ "A pending task was found %r, but it is only %s hours" " old",
245
+ existing_task["id"],
246
+ time_since_last_updated,
247
+ )
248
+ else:
249
+ log.info(
250
+ "A pending task was found %s for this resource, so "
251
+ "skipping this duplicate task",
252
+ existing_task["id"],
253
+ )
254
+ return False
255
+
256
+ task["id"] = existing_task["id"]
257
+ except toolkit.ObjectNotFound:
258
+ pass
259
+
260
+ callback_url = toolkit.url_for(
261
+ "api.action", ver=3, logic_function="csvwmapandtransform_hook", qualified=True
262
+ )
263
+ # initioalize database for additional job data
264
+ db.init()
265
+ # Store details of the job in the db
266
+
267
+ # add this dataset to the queue
268
+ job = toolkit.enqueue_job(
269
+ transform,
270
+ [res_url, res_id, dataset_id, callback_url, task["last_updated"]],
271
+ title='csvwmapandtransform {} "{}" {}'.format(operation, res_name, res_url),
272
+ queue=queue, # , timeout=JOB_TIMEOUT
273
+ )
274
+ try:
275
+ db.add_pending_job(job.id, job_type=task["task_type"], result_url=callback_url)
276
+ except sa.exc.IntegrityError:
277
+ raise Exception("job_id {} already exists".format(task["id"]))
278
+
279
+ # log.info("added a job to csvwmapandtransform database")
280
+ # log.debug("enqueued job id".format(job.id)
281
+
282
+ log.debug("Enqueued job {} to {} resource {}".format(job.id, operation, res_name))
283
+
284
+ value = json.dumps({"job_id": job.id})
285
+ task["value"] = value
286
+ task["state"] = "pending"
287
+ task["last_updated"] = str(datetime.datetime.utcnow())
288
+ toolkit.get_action("task_status_update")(
289
+ {"session": model.meta.create_local_session(), "ignore_auth": True}, task
290
+ )
291
+ return True
292
+
293
+
294
+ def csvwmapandtransform_hook(context, data_dict):
295
+ """Update csvwmapandtransform task. This action is typically called by ckanext-csvwmapandtransform
296
+ whenever the status of a job changes.
297
+
298
+ :param metadata: metadata provided when submitting job. key-value pairs.
299
+ Must have resource_id property.
300
+ :type metadata: dict
301
+ :param status: status of the job from the csvwmapandtransform service. Allowed values:
302
+ pending, running, running_but_viewable, complete, error
303
+ (which must all be valid values for task_status too)
304
+ :type status: string
305
+ :param error: Error raised during job execution
306
+ :type error: string
307
+
308
+ NB here are other params which are in the equivalent object in
309
+ ckan-service-provider (from job_status):
310
+ :param sent_data: Input data for job
311
+ :type sent_data: json encodable data
312
+ :param job_id: An identifier for the job
313
+ :type job_id: string
314
+ :param result_url: Callback url
315
+ :type result_url: url string
316
+ :param data: Results from job.
317
+ :type data: json encodable data
318
+ :param requested_timestamp: Time the job started
319
+ :type requested_timestamp: timestamp
320
+ :param finished_timestamp: Time the job finished
321
+ :type finished_timestamp: timestamp
322
+
323
+ """
324
+
325
+ metadata, status, job_info = toolkit.get_or_bust(
326
+ data_dict, ["metadata", "status", "job_info"]
327
+ )
328
+
329
+ res_id = toolkit.get_or_bust(metadata, "resource_id")
330
+
331
+ # Pass metadata, not data_dict, as it contains the resource id needed
332
+ # on the auth checks
333
+ # toolkit.check_access('xloader_submit', context, metadata)
334
+
335
+ task = toolkit.get_action("task_status_show")(
336
+ context,
337
+ {
338
+ "entity_id": res_id,
339
+ "task_type": "csvwmapandtransform",
340
+ "key": "csvwmapandtransform",
341
+ },
342
+ )
343
+
344
+ task["state"] = status
345
+ task["last_updated"] = str(datetime.datetime.utcnow())
346
+ task["error"] = data_dict.get("error")
347
+ # task['task_info'] = job_info
348
+ resubmit = False
349
+
350
+ if status in ("complete", "running_but_viewable"):
351
+ # Create default views for resource if necessary (only the ones that
352
+ # require data to be in the DataStore)
353
+ resource_dict = toolkit.get_action("resource_show")(context, {"id": res_id})
354
+
355
+ dataset_dict = toolkit.get_action("package_show")(
356
+ context, {"id": resource_dict["package_id"]}
357
+ )
358
+
359
+ # Check if the uploaded file has been modified in the meantime
360
+ if resource_dict.get("last_modified") and metadata.get("task_created"):
361
+ try:
362
+ last_modified_datetime = parse_date(resource_dict["last_modified"])
363
+ task_created_datetime = parse_date(metadata["task_created"])
364
+ if last_modified_datetime > task_created_datetime:
365
+ log.debug(
366
+ "Uploaded file more recent: %s > %s",
367
+ last_modified_datetime,
368
+ task_created_datetime,
369
+ )
370
+ resubmit = True
371
+ except ValueError:
372
+ pass
373
+ # Check if the URL of the file has been modified in the meantime
374
+ elif (
375
+ resource_dict.get("url")
376
+ and metadata.get("original_url")
377
+ and resource_dict["url"] != metadata["original_url"]
378
+ ):
379
+ log.debug(
380
+ "URLs are different: %s != %s",
381
+ resource_dict["url"],
382
+ metadata["original_url"],
383
+ )
384
+ resubmit = True
385
+ # mark job completed in db
386
+ log.debug(task)
387
+ log.debug(job_info)
388
+
389
+ if status == "complete":
390
+ log.debug("job complete now update job db at: {}".format(task))
391
+ db.init()
392
+ job_id = json.loads(task["value"])["job_id"]
393
+ db.mark_job_as_completed(job_id)
394
+
395
+ context["ignore_auth"] = True
396
+ toolkit.get_action("task_status_update")(context, task)
397
+
398
+ if resubmit:
399
+ log.debug(
400
+ "Resource %s has been modified, " "resubmitting to csvwmapandtransform",
401
+ res_id,
402
+ )
403
+ toolkit.get_action("csvwmapandtransform_transform")(
404
+ context, {"resource_id": res_id}
405
+ )
File without changes
@@ -0,0 +1,81 @@
1
+ ckan.module('csvwmapandtransform', function (jQuery) {
2
+ return {
3
+ options: {
4
+ parameters: {
5
+ html: {
6
+ contentType: 'application/json', // change the content type to text/html
7
+ dataType: 'json', // change the data type to html
8
+ dataConverter: function (data) { return data; },
9
+ language: 'json'
10
+ }
11
+ }
12
+ },
13
+ initialize: function () {
14
+ var self = this;
15
+ var p;
16
+ p = this.options.parameters.html;
17
+ console.log("Initialized csvwmapandtransform for element: ", this.el);
18
+ const apiUrl = this.el.get(0).getAttribute("data-api-url"); // Fetch the API URL from the parent element
19
+ if (!apiUrl) {
20
+ console.error("No API URL specified for counter:", this.el);
21
+ return;
22
+ }
23
+ var log_length;
24
+ log_length = 0;
25
+ var update = function () { // define the update function
26
+ jQuery.ajax({
27
+ url: apiUrl,
28
+ type: 'GET',
29
+ contentType: p.contentType,
30
+ dataType: p.dataType,
31
+ data: { get_param: 'value' },
32
+ success: function (data) {
33
+ const haslogs = 'logs' in data.status;
34
+ const hasresults = data.status?.data?.files;
35
+ if (hasresults || haslogs) {
36
+ // console.log(self.el.find('button[name="delete"]'));
37
+ self.el.find('button[name="delete"]').removeClass("invisible");
38
+ self.el.find('div[name="status"]').removeClass("invisible");
39
+ };
40
+ // console.log(haslogs, hasresults);
41
+ if (!haslogs) return;
42
+ var length = Object.keys(data.status.logs).length;
43
+ if (length) {
44
+ if (length !== log_length) {
45
+ // self.el.html(JSON.stringify(data, null, 2)); // update the HTML if there are changes
46
+ var logs_div = $(self.el).find('ul[name="log"]');
47
+ jQuery.each(data.status.logs, function (key, value) {
48
+ if (key + 1 < log_length) return;
49
+ logs_div.append("<li class='item "
50
+ + value.class +
51
+ "'><i class='fa icon fa-"
52
+ + value.icon +
53
+ "'></i><div class='alert alert-"
54
+ + value.alertlevel +
55
+ " mb-0 mt-3' role='alert'>"
56
+ + value.message +
57
+ "</div><span class='date' title='timestamp'>"
58
+ + value.timestamp +
59
+ "</span></li>");
60
+ console.log("Appending log:", value);
61
+ });
62
+ console.log("csvwmapandtransform: status updated");
63
+ log_length = length;
64
+ }
65
+ } else {
66
+ // console.log('no log changes');
67
+ }
68
+ },
69
+ error: function (xhr, status, error) {
70
+ console.log('Error:', error);
71
+ },
72
+ complete: function () {
73
+ // call the update function recursively after a delay
74
+ setTimeout(update, 2000);
75
+ }
76
+ });
77
+ };
78
+ update(); // call the update function immediately after initialization
79
+ }
80
+ };
81
+ });
@@ -0,0 +1,124 @@
1
+ .activity {
2
+ padding: 0;
3
+ list-style-type: none;
4
+ }
5
+
6
+ .activity .item {
7
+ position: relative;
8
+ margin: 0 0 15px 0;
9
+ padding: 0;
10
+ }
11
+
12
+ .activity .item .user-image {
13
+ border-radius: 100px;
14
+ }
15
+
16
+ .activity .item .date {
17
+ color: #999999;
18
+ font-size: 12px;
19
+ }
20
+
21
+ .activity .item.no-avatar p {
22
+ margin-left: 40px;
23
+ }
24
+
25
+ .activity.activity-empty {
26
+ background: none;
27
+ }
28
+
29
+
30
+ @media (min-width: 768px) {
31
+ .activity {
32
+ background: transparent url("/dotted.png") 14px 0 repeat-y;
33
+ }
34
+
35
+ .activity .item .date {
36
+ margin: 5px 0 0 80px;
37
+ }
38
+ }
39
+
40
+ .activity .item .icon {
41
+ display: block;
42
+ position: absolute;
43
+ top: 0;
44
+ left: 0;
45
+ width: 30px;
46
+ height: 30px;
47
+ line-height: 30px;
48
+ text-align: center;
49
+ color: #FFFFFF;
50
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
51
+ /* font-weight: normal; */
52
+ margin-right: 10px;
53
+ border-radius: 100px;
54
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
55
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);
56
+ }
57
+
58
+ .activity .item .alert {
59
+ margin: 5px 0 0 40px;
60
+ }
61
+
62
+ .activity_buttons {
63
+ display: flex;
64
+ gap: 0.5rem;
65
+ }
66
+
67
+ .activity_buttons>a.btn.disabled {
68
+ color: inherit;
69
+ opacity: 0.7;
70
+ }
71
+
72
+ .popover {
73
+ width: 300px;
74
+ }
75
+
76
+ .popover .popover-title {
77
+ font-weight: bold;
78
+ margin-bottom: 0;
79
+ }
80
+
81
+ .popover p.about {
82
+ margin: 0 0 10px 0;
83
+ }
84
+
85
+ .popover .popover-close {
86
+ float: right;
87
+ text-decoration: none;
88
+ }
89
+
90
+ .popover .empty {
91
+ padding: 10px;
92
+ color: #6E6E6E;
93
+ font-style: italic;
94
+ }
95
+
96
+
97
+ .activity .item p {
98
+ font-size: 14px;
99
+ line-height: 1.5;
100
+ margin: 5px 0 0 40px;
101
+ }
102
+
103
+ .activity .item.failure .icon {
104
+ background: #B95252;
105
+ }
106
+
107
+ .activity .item.success .icon {
108
+ background: #69A67A;
109
+ }
110
+
111
+ .select-time {
112
+ width: 250px;
113
+ display: inline;
114
+ }
115
+
116
+ br.line-height2 {
117
+ line-height: 2;
118
+ }
119
+
120
+ .pull-right {
121
+ float: right;
122
+ }
123
+
124
+ /*# sourceMappingURL=activity.css.map */
@@ -0,0 +1,13 @@
1
+ js:
2
+ filters: rjsmin
3
+ output: csvwmapandtransform/csvwmapandtransform.js
4
+ contents:
5
+ - script.js
6
+ extra:
7
+ preload:
8
+ - base/main
9
+
10
+ style:
11
+ contents:
12
+ - style.css
13
+ output: csvwmapandtransform/csvwmapandtransform.css
@@ -0,0 +1,23 @@
1
+ from ckan.logic.auth.get import task_status_show
2
+
3
+ import ckanext.datastore.logic.auth as auth
4
+
5
+
6
+ def csvwmapandtransform_transform(context, data_dict):
7
+ if "resource" in data_dict and data_dict["resource"].get("package_id"):
8
+ data_dict["id"] = data_dict["resource"].get("package_id")
9
+ privilege = "package_update"
10
+ else:
11
+ privilege = "resource_update"
12
+ return auth.datastore_auth(context, data_dict, privilege=privilege)
13
+
14
+
15
+ def csvwmapandtransform_transform_status(context, data_dict):
16
+ return task_status_show(context, data_dict)
17
+
18
+
19
+ def get_auth_functions():
20
+ return {
21
+ "csvwmapandtransform_transform": csvwmapandtransform_transform,
22
+ "csvwmapandtransform_transform_status": csvwmapandtransform_transform_status,
23
+ }
@@ -0,0 +1,18 @@
1
+ import click
2
+
3
+
4
+ @click.group(short_help="csvwmapandtransform CLI.")
5
+ def csvwmapandtransform():
6
+ """csvwmapandtransform CLI."""
7
+ pass
8
+
9
+
10
+ @csvwmapandtransform.command()
11
+ @click.argument("name", default="csvwmapandtransform")
12
+ def command(name):
13
+ """Docs."""
14
+ click.echo("Hello, {name}!".format(name=name))
15
+
16
+
17
+ def get_commands():
18
+ return [csvwmapandtransform]