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,397 @@
1
+ """
2
+ Abstracts a database. Used for storing logging when it aiembeddings a resource into
3
+ DataStore.
4
+
5
+ Loosely based on ckan-service-provider's db.py
6
+ """
7
+
8
+ """
9
+ Abstracts a database. Used for storing logging when it aiembeddings a resource into
10
+ DataStore.
11
+
12
+ Loosely based on ckan-service-provider's db.py
13
+ """
14
+
15
+ import datetime
16
+ import json
17
+
18
+ import six
19
+ import sqlalchemy
20
+ from ckan.plugins import toolkit
21
+
22
+ ENGINE = None
23
+ _METADATA = None
24
+ JOBS_TABLE = None
25
+ METADATA_TABLE = None
26
+ LOGS_TABLE = None
27
+
28
+
29
+ def init(db_uri: str = "", echo=False):
30
+ """Initialise the database.
31
+
32
+ Initialise the sqlalchemy engine, metadata and table objects that we use to
33
+ connect to the database.
34
+
35
+ Create the database and the database tables themselves if they don't
36
+ already exist.
37
+
38
+ :param uri: the sqlalchemy database URI
39
+ :type uri: string
40
+
41
+ :param echo: whether or not to have the sqlalchemy engine log all
42
+ statements to stdout
43
+ :type echo: bool
44
+
45
+ """
46
+ if not db_uri:
47
+ db_uri = toolkit.config.get("ckanext.csvwmapandtransform.db_url")
48
+ global ENGINE, _METADATA, JOBS_TABLE, METADATA_TABLE, LOGS_TABLE
49
+ ENGINE = sqlalchemy.create_engine(db_uri, echo=echo, convert_unicode=True)
50
+ _METADATA = sqlalchemy.MetaData(ENGINE)
51
+ JOBS_TABLE = _init_jobs_table()
52
+ METADATA_TABLE = _init_metadata_table()
53
+ LOGS_TABLE = _init_logs_table()
54
+ _METADATA.create_all(ENGINE)
55
+
56
+
57
+ def drop_all():
58
+ """Delete all the database tables (if they exist).
59
+
60
+ This is for tests to reset the DB. Note that this will delete *all* tables
61
+ in the database, not just tables created by this module (for example
62
+ apscheduler's tables will also be deleted).
63
+
64
+ """
65
+ if _METADATA:
66
+ _METADATA.drop_all(ENGINE)
67
+
68
+
69
+ def delete_job(job_id):
70
+ """Delete a job from the jobs table by job_id.
71
+
72
+ :param job_id: the job_id of the job to be deleted
73
+ :type job_id: unicode
74
+ """
75
+ if job_id:
76
+ job_id = six.text_type(job_id)
77
+
78
+ msg = ""
79
+ with ENGINE.connect() as conn:
80
+ trans = conn.begin()
81
+ try:
82
+ result = conn.execute(
83
+ JOBS_TABLE.delete().where(JOBS_TABLE.c.job_id == job_id)
84
+ )
85
+ if result.rowcount == 0:
86
+ msg = f"No job found with id: {job_id}"
87
+ else:
88
+ msg = f"Job with id: {job_id} has been deleted successfully."
89
+ trans.commit()
90
+ except Exception as e:
91
+ trans.rollback()
92
+ msg = f"An error occurred: {e}"
93
+ return msg
94
+
95
+
96
+ def get_job(job_id):
97
+ """Return the job with the given job_id as a dict."""
98
+ if job_id:
99
+ job_id = six.text_type(job_id)
100
+
101
+ with ENGINE.connect() as conn:
102
+ result = conn.execute(
103
+ JOBS_TABLE.select().where(JOBS_TABLE.c.job_id == job_id)
104
+ ).first()
105
+
106
+ if not result:
107
+ return None
108
+
109
+ result_dict = {
110
+ field: (
111
+ value.isoformat()
112
+ if isinstance(value := getattr(result, field), datetime.datetime)
113
+ else value
114
+ )
115
+ for field in result.keys()
116
+ }
117
+
118
+ result_dict["metadata"] = _get_metadata(job_id)
119
+ result_dict["logs"] = _get_logs(job_id)
120
+
121
+ return result_dict
122
+
123
+
124
+ def add_pending_job(job_id, job_type, data=None, metadata=None, result_url=None):
125
+ """Add a new job with status "pending" to the jobs table."""
126
+ if not data:
127
+ data = {}
128
+ data = six.text_type(json.dumps(data))
129
+
130
+ if job_id:
131
+ job_id = six.text_type(job_id)
132
+ if job_type:
133
+ job_type = six.text_type(job_type)
134
+ if result_url:
135
+ result_url = six.text_type(result_url)
136
+
137
+ if not metadata:
138
+ metadata = {}
139
+
140
+ with ENGINE.connect() as conn:
141
+ trans = conn.begin()
142
+ try:
143
+ conn.execute(
144
+ JOBS_TABLE.insert().values(
145
+ job_id=job_id,
146
+ job_type=job_type,
147
+ status="pending",
148
+ requested_timestamp=datetime.datetime.utcnow(),
149
+ sent_data=data,
150
+ result_url=result_url,
151
+ )
152
+ )
153
+
154
+ inserts = [
155
+ {
156
+ "job_id": job_id,
157
+ "key": six.text_type(key),
158
+ "value": six.text_type(
159
+ json.dumps(value)
160
+ if not isinstance(value, six.string_types)
161
+ else value
162
+ ),
163
+ "type": (
164
+ "json" if not isinstance(value, six.string_types) else "string"
165
+ ),
166
+ }
167
+ for key, value in metadata.items()
168
+ ]
169
+
170
+ if inserts:
171
+ conn.execute(METADATA_TABLE.insert(), inserts)
172
+ trans.commit()
173
+ except Exception:
174
+ trans.rollback()
175
+ raise
176
+
177
+
178
+ class InvalidErrorObjectError(Exception):
179
+ pass
180
+
181
+
182
+ def _validate_error(error):
183
+ """Validate and return the given error object.
184
+
185
+ Based on the given error object, return either None or a dict with a
186
+ "message" key whose value is a string (the dict may also have any other
187
+ keys that it wants).
188
+
189
+ The given "error" object can be:
190
+
191
+ - None, in which case None is returned
192
+
193
+ - A string, in which case a dict like this will be returned:
194
+ {"message": error_string}
195
+
196
+ - A dict with a "message" key whose value is a string, in which case the
197
+ dict will be returned unchanged
198
+
199
+ :param error: the error object to validate
200
+
201
+ :raises InvalidErrorObjectError: If the error object doesn't match any of
202
+ the allowed types
203
+
204
+ """
205
+ if error is None:
206
+ return None
207
+ elif isinstance(error, six.string_types):
208
+ return {"message": error}
209
+ else:
210
+ try:
211
+ message = error["message"]
212
+ if isinstance(message, six.string_types):
213
+ return error
214
+ else:
215
+ raise InvalidErrorObjectError("error['message'] must be a string")
216
+ except (TypeError, KeyError):
217
+ raise InvalidErrorObjectError(
218
+ "error must be either a string or a dict with a message key"
219
+ )
220
+
221
+
222
+ def _update_job(job_id, job_dict):
223
+ """Update the database row for the given job_id with the given job_dict."""
224
+ if job_id:
225
+ job_id = six.text_type(job_id)
226
+
227
+ if "error" in job_dict:
228
+ job_dict["error"] = json.dumps(_validate_error(job_dict["error"]))
229
+ job_dict["error"] = six.text_type(job_dict["error"])
230
+
231
+ if "data" in job_dict:
232
+ job_dict["data"] = six.text_type(job_dict["data"])
233
+
234
+ with ENGINE.connect() as conn:
235
+ conn.execute(
236
+ JOBS_TABLE.update().where(JOBS_TABLE.c.job_id == job_id).values(**job_dict)
237
+ )
238
+
239
+
240
+ def mark_job_as_completed(job_id, data=None):
241
+ """Mark a job as completed successfully.
242
+
243
+ :param job_id: the job_id of the job to be updated
244
+ :type job_id: unicode
245
+
246
+ :param data: the output data returned by the job
247
+ :type data: any JSON-serializable type (including None)
248
+
249
+ """
250
+ update_dict = {
251
+ "status": "complete",
252
+ "data": json.dumps(data),
253
+ "finished_timestamp": datetime.datetime.utcnow(),
254
+ }
255
+ _update_job(job_id, update_dict)
256
+
257
+
258
+ def mark_job_as_missed(job_id):
259
+ """Mark a job as missed because it was in the queue for too long.
260
+
261
+ :param job_id: the job_id of the job to be updated
262
+ :type job_id: unicode
263
+
264
+ """
265
+ update_dict = {
266
+ "status": "error",
267
+ "error": "Job delayed too long, service full",
268
+ "finished_timestamp": datetime.datetime.utcnow(),
269
+ }
270
+ _update_job(job_id, update_dict)
271
+
272
+
273
+ def mark_job_as_errored(job_id, error_object):
274
+ """Mark a job as failed with an error.
275
+
276
+ :param job_id: the job_id of the job to be updated
277
+ :type job_id: unicode
278
+
279
+ :param error_object: the error returned by the job
280
+ :type error_object: either a string or a dict with a "message" key whose
281
+ value is a string
282
+
283
+ """
284
+ update_dict = {
285
+ "status": "error",
286
+ "error": error_object,
287
+ "finished_timestamp": datetime.datetime.utcnow(),
288
+ }
289
+ _update_job(job_id, update_dict)
290
+
291
+
292
+ def mark_job_as_failed_to_post_result(job_id):
293
+ """Mark a job as 'failed to post result'.
294
+
295
+ This happens when a job completes (either successfully or with an error)
296
+ then trying to post the job result back to the job's callback URL fails.
297
+
298
+ FIXME: This overwrites any error from the job itself!
299
+
300
+ :param job_id: the job_id of the job to be updated
301
+ :type job_id: unicode
302
+
303
+ """
304
+ update_dict = {
305
+ "error": "Process completed but unable to post to result_url",
306
+ }
307
+ _update_job(job_id, update_dict)
308
+
309
+
310
+ def _init_jobs_table():
311
+ """Initialise the "jobs" table in the db."""
312
+ _jobs_table = sqlalchemy.Table(
313
+ "jobs",
314
+ _METADATA,
315
+ sqlalchemy.Column("job_id", sqlalchemy.UnicodeText, primary_key=True),
316
+ sqlalchemy.Column("job_type", sqlalchemy.UnicodeText),
317
+ sqlalchemy.Column("status", sqlalchemy.UnicodeText, index=True),
318
+ sqlalchemy.Column("data", sqlalchemy.UnicodeText),
319
+ sqlalchemy.Column("error", sqlalchemy.UnicodeText),
320
+ sqlalchemy.Column("requested_timestamp", sqlalchemy.DateTime),
321
+ sqlalchemy.Column("finished_timestamp", sqlalchemy.DateTime),
322
+ sqlalchemy.Column("sent_data", sqlalchemy.UnicodeText),
323
+ # Callback URL:
324
+ sqlalchemy.Column("result_url", sqlalchemy.UnicodeText),
325
+ )
326
+ return _jobs_table
327
+
328
+
329
+ def _init_metadata_table():
330
+ """Initialise the "metadata" table in the db."""
331
+ _metadata_table = sqlalchemy.Table(
332
+ "metadata",
333
+ _METADATA,
334
+ sqlalchemy.Column(
335
+ "job_id",
336
+ sqlalchemy.ForeignKey("jobs.job_id", ondelete="CASCADE"),
337
+ nullable=False,
338
+ primary_key=True,
339
+ ),
340
+ sqlalchemy.Column("key", sqlalchemy.UnicodeText, primary_key=True),
341
+ sqlalchemy.Column("value", sqlalchemy.UnicodeText, index=True),
342
+ sqlalchemy.Column("type", sqlalchemy.UnicodeText),
343
+ )
344
+ return _metadata_table
345
+
346
+
347
+ def _init_logs_table():
348
+ """Initialise the "logs" table in the db."""
349
+ _logs_table = sqlalchemy.Table(
350
+ "logs",
351
+ _METADATA,
352
+ sqlalchemy.Column(
353
+ "job_id",
354
+ sqlalchemy.ForeignKey("jobs.job_id", ondelete="CASCADE"),
355
+ nullable=False,
356
+ ),
357
+ sqlalchemy.Column("timestamp", sqlalchemy.DateTime),
358
+ sqlalchemy.Column("message", sqlalchemy.UnicodeText),
359
+ sqlalchemy.Column("level", sqlalchemy.UnicodeText),
360
+ sqlalchemy.Column("module", sqlalchemy.UnicodeText),
361
+ sqlalchemy.Column("funcName", sqlalchemy.UnicodeText),
362
+ sqlalchemy.Column("lineno", sqlalchemy.Integer),
363
+ )
364
+ return _logs_table
365
+
366
+
367
+ def _get_metadata(job_id):
368
+ """Return any metadata for the given job_id from the metadata table."""
369
+ job_id = six.text_type(job_id)
370
+
371
+ with ENGINE.connect() as conn:
372
+ results = conn.execute(
373
+ METADATA_TABLE.select().where(METADATA_TABLE.c.job_id == job_id)
374
+ ).fetchall()
375
+
376
+ metadata = {
377
+ row["key"]: json.loads(row["value"]) if row["type"] == "json" else row["value"]
378
+ for row in results
379
+ }
380
+ return metadata
381
+
382
+
383
+ def _get_logs(job_id):
384
+ """Return any logs for the given job_id from the logs table."""
385
+ job_id = six.text_type(job_id)
386
+
387
+ with ENGINE.connect() as conn:
388
+ results = conn.execute(
389
+ LOGS_TABLE.select().where(LOGS_TABLE.c.job_id == job_id)
390
+ ).fetchall()
391
+
392
+ results = [dict(result) for result in results]
393
+
394
+ for result in results:
395
+ result.pop("job_id")
396
+
397
+ return results
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ import re
4
+ from typing import Any
5
+
6
+ import ckan.plugins.toolkit as toolkit
7
+ import requests
8
+
9
+ log = __import__("logging").getLogger(__name__)
10
+
11
+
12
+ def csvwmapandtransform__status_description(status: dict[str, Any]):
13
+ _ = toolkit._
14
+
15
+ if status.get("status"):
16
+ captions = {
17
+ "complete": _("Complete"),
18
+ "pending": _("Pending"),
19
+ "submitting": _("Submitting"),
20
+ "error": _("Error"),
21
+ }
22
+
23
+ return captions.get(status["status"], status["status"].capitalize())
24
+ else:
25
+ return _("Not Uploaded Yet")
26
+
27
+
28
+ def common_member(a, b):
29
+ return any(i in b for i in a)
30
+
31
+
32
+ def csvwmapandtransform_show_tools(resource):
33
+ formats = toolkit.config.get("ckanext.csvwmapandtransform.formats")
34
+
35
+ format_parts = re.split("/|;", resource["format"].lower().replace(" ", ""))
36
+ if common_member(format_parts, formats):
37
+ return True
38
+ else:
39
+ False
40
+
41
+
42
+ def csvwmapandtransform_service_available():
43
+ url = toolkit.config.get("ckanext.csvwmapandtransform.maptomethod_url")
44
+ ssl_verify = toolkit.config.get("ckanext.csvwmapandtransform.ssl_verify")
45
+ # log.debug(f"mapomethodurl: {url} {bool(url)}")
46
+ if not url:
47
+ return False # If EXTRACT_URL is not set, return False
48
+ try:
49
+ # Perform a HEAD request (lightweight check) to see if the service responds
50
+ response = requests.head(url, timeout=5, verify=ssl_verify)
51
+ # log.debug(f"reponse: {response}")
52
+ if (200 <= response.status_code < 400) or response.status_code == 405:
53
+ return True # URL is reachable and returns a valid status code
54
+ else:
55
+ return False # URL is reachable but response status is not valid
56
+ except requests.RequestException as e:
57
+ # If there's any issue (timeout, connection error, etc.)
58
+ # log.debug(e)
59
+ return False
60
+
61
+
62
+ def get_helpers():
63
+ return {
64
+ "csvwmapandtransform__status_description": csvwmapandtransform__status_description,
65
+ "csvwmapandtransform_show_tools": csvwmapandtransform_show_tools,
66
+ "csvwmapandtransform_service_available": csvwmapandtransform_service_available,
67
+ }
File without changes
@@ -0,0 +1,108 @@
1
+ # Translations template for ckanext-csvwmapandtransform.
2
+ # Copyright (C) 2025 ORGANIZATION
3
+ # This file is distributed under the same license as the
4
+ # ckanext-csvwmapandtransform project.
5
+ # FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
6
+ #
7
+ #, fuzzy
8
+ msgid ""
9
+ msgstr ""
10
+ "Project-Id-Version: ckanext-csvwmapandtransform 0.0.0\n"
11
+ "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
12
+ "POT-Creation-Date: 2025-05-14 07:19+0000\n"
13
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
15
+ "Language-Team: LANGUAGE <LL@li.org>\n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=utf-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Generated-By: Babel 2.15.0\n"
20
+
21
+ #: ckanext/csvwmapandtransform/helpers.py:17
22
+ msgid "Complete"
23
+ msgstr ""
24
+
25
+ #: ckanext/csvwmapandtransform/helpers.py:18
26
+ msgid "Pending"
27
+ msgstr ""
28
+
29
+ #: ckanext/csvwmapandtransform/helpers.py:19
30
+ msgid "Submitting"
31
+ msgstr ""
32
+
33
+ #: ckanext/csvwmapandtransform/helpers.py:20
34
+ msgid "Error"
35
+ msgstr ""
36
+
37
+ #: ckanext/csvwmapandtransform/helpers.py:25
38
+ msgid "Not Uploaded Yet"
39
+ msgstr ""
40
+
41
+ #: ckanext/csvwmapandtransform/views.py:29 ckanext/csvwmapandtransform/views.py:52
42
+ #: ckanext/csvwmapandtransform/views.py:89 ckanext/csvwmapandtransform/views.py:163
43
+ msgid "Not authorized to see this page"
44
+ msgstr ""
45
+
46
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/create_mapping.html:13
47
+ msgid "Map"
48
+ msgstr ""
49
+
50
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/create_mapping.html:19
51
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:24
52
+ msgid "View resource"
53
+ msgstr ""
54
+
55
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/create_mapping.html:28
56
+ msgid "Map resource"
57
+ msgstr ""
58
+
59
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:18
60
+ #: ckanext/csvwmapandtransform/templates/package/resource_read.html:6
61
+ #: ckanext/csvwmapandtransform/templates/package/snippets/resource_item.html:17
62
+ msgid "Transform"
63
+ msgstr ""
64
+
65
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:30
66
+ msgid "Edit resource"
67
+ msgstr ""
68
+
69
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:32
70
+ msgid "Views"
71
+ msgstr ""
72
+
73
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:36
74
+ msgid "Transform Status"
75
+ msgstr ""
76
+
77
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:45
78
+ msgid "Run Transformation"
79
+ msgstr ""
80
+
81
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:48
82
+ msgid "Delete"
83
+ msgstr ""
84
+
85
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:63
86
+ msgid "The status of the service (Green means available, Red means unavailable)"
87
+ msgstr ""
88
+
89
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:78
90
+ msgid "Status"
91
+ msgstr ""
92
+
93
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:82
94
+ msgid "Last updated"
95
+ msgstr ""
96
+
97
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:87
98
+ msgid "Never"
99
+ msgstr ""
100
+
101
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:92
102
+ msgid "Graph Update Log"
103
+ msgstr ""
104
+
105
+ #: ckanext/csvwmapandtransform/templates/package/snippets/resource_item.html:10
106
+ msgid "Create Mapping"
107
+ msgstr ""
108
+
@@ -0,0 +1,113 @@
1
+ # German translations for ckanext-csvwmapandtransform.
2
+ # Copyright (C) 2025 ORGANIZATION
3
+ # This file is distributed under the same license as the
4
+ # ckanext-csvwmapandtransform project.
5
+ # FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
6
+ #
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: ckanext-csvwmapandtransform 0.0.0\n"
10
+ "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
11
+ "POT-Creation-Date: 2025-05-14 07:19+0000\n"
12
+ "PO-Revision-Date: 2025-05-14 07:20+0000\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language: de\n"
15
+ "Language-Team: de <LL@li.org>\n"
16
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
17
+ "MIME-Version: 1.0\n"
18
+ "Content-Type: text/plain; charset=utf-8\n"
19
+ "Content-Transfer-Encoding: 8bit\n"
20
+ "Generated-By: Babel 2.15.0\n"
21
+
22
+ #: ckanext/csvwmapandtransform/helpers.py:17
23
+ msgid "Complete"
24
+ msgstr "Vollständig"
25
+
26
+ #: ckanext/csvwmapandtransform/helpers.py:18
27
+ msgid "Pending"
28
+ msgstr "Wartet"
29
+
30
+ #: ckanext/csvwmapandtransform/helpers.py:19
31
+ msgid "Submitting"
32
+ msgstr "Übersenden"
33
+
34
+ #: ckanext/csvwmapandtransform/helpers.py:20
35
+ msgid "Error"
36
+ msgstr "Fehler"
37
+
38
+ #: ckanext/csvwmapandtransform/helpers.py:25
39
+ msgid "Not Uploaded Yet"
40
+ msgstr "Noch nicht Hochgeladen"
41
+
42
+ #: ckanext/csvwmapandtransform/views.py:29
43
+ #: ckanext/csvwmapandtransform/views.py:52
44
+ #: ckanext/csvwmapandtransform/views.py:89
45
+ #: ckanext/csvwmapandtransform/views.py:163
46
+ msgid "Not authorized to see this page"
47
+ msgstr "Sie sind nicht berechtigt dies Seite aufzurufen"
48
+
49
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/create_mapping.html:13
50
+ msgid "Map"
51
+ msgstr "Map"
52
+
53
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/create_mapping.html:19
54
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:24
55
+ msgid "View resource"
56
+ msgstr "Ressource Anzeigen"
57
+
58
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/create_mapping.html:28
59
+ msgid "Map resource"
60
+ msgstr "Ressource mappen"
61
+
62
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:18
63
+ #: ckanext/csvwmapandtransform/templates/package/resource_read.html:6
64
+ #: ckanext/csvwmapandtransform/templates/package/snippets/resource_item.html:17
65
+ msgid "Transform"
66
+ msgstr "Transformieren"
67
+
68
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:30
69
+ msgid "Edit resource"
70
+ msgstr "Ressource bearbeiten"
71
+
72
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:32
73
+ msgid "Views"
74
+ msgstr "Ansichten"
75
+
76
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:36
77
+ msgid "Transform Status"
78
+ msgstr "Transformationsstatus"
79
+
80
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:45
81
+ msgid "Run Transformation"
82
+ msgstr "Transformation ausführen"
83
+
84
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:48
85
+ msgid "Delete"
86
+ msgstr "Löschen"
87
+
88
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:63
89
+ msgid "The status of the service (Green means available, Red means unavailable)"
90
+ msgstr "Status des Service (Grün heißt verfügbar, rot nicht verfügbar)"
91
+
92
+
93
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:78
94
+ msgid "Status"
95
+ msgstr "Status"
96
+
97
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:82
98
+ msgid "Last updated"
99
+ msgstr "Letztes Update"
100
+
101
+
102
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:87
103
+ msgid "Never"
104
+ msgstr "Niemals"
105
+
106
+ #: ckanext/csvwmapandtransform/templates/csvwmapandtransform/transform.html:92
107
+ msgid "Graph Update Log"
108
+ msgstr "Graphbearbeitungsbericht"
109
+
110
+ #: ckanext/csvwmapandtransform/templates/package/snippets/resource_item.html:10
111
+ msgid "Create Mapping"
112
+ msgstr "Mapping erzeugen"
113
+