tg-prepare 1.0.0__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 tg-prepare might be problematic. Click here for more details.

Files changed (46) hide show
  1. tg_prepare-1.0.0.dist-info/METADATA +19 -0
  2. tg_prepare-1.0.0.dist-info/RECORD +46 -0
  3. tg_prepare-1.0.0.dist-info/WHEEL +5 -0
  4. tg_prepare-1.0.0.dist-info/entry_points.txt +3 -0
  5. tg_prepare-1.0.0.dist-info/licenses/LICENSE +202 -0
  6. tg_prepare-1.0.0.dist-info/top_level.txt +2 -0
  7. tgp_backend/__init__.py +18 -0
  8. tgp_backend/auth.py +71 -0
  9. tgp_backend/cli.py +22 -0
  10. tgp_backend/directories.py +85 -0
  11. tgp_backend/interfaces.py +3 -0
  12. tgp_backend/nextcloud.py +125 -0
  13. tgp_backend/project.py +267 -0
  14. tgp_backend/tgclient.py +92 -0
  15. tgp_backend/util.py +83 -0
  16. tgp_ui/__init__.py +0 -0
  17. tgp_ui/app.py +424 -0
  18. tgp_ui/static/css/bootstrap-icons.min.css +5 -0
  19. tgp_ui/static/css/bootstrap.min.css +7 -0
  20. tgp_ui/static/css/main.css +146 -0
  21. tgp_ui/static/css/simpleXML.css +67 -0
  22. tgp_ui/static/js/bootstrap.bundle.min.js +7 -0
  23. tgp_ui/static/js/jquery.min.js +2 -0
  24. tgp_ui/static/js/jstree.min.js +3 -0
  25. tgp_ui/static/js/main.js +454 -0
  26. tgp_ui/static/js/simpleXML.js +193 -0
  27. tgp_ui/templates/collection.html +194 -0
  28. tgp_ui/templates/empty.html +4 -0
  29. tgp_ui/templates/file_tree.html +39 -0
  30. tgp_ui/templates/file_upload.html +24 -0
  31. tgp_ui/templates/jsfiler.html +6 -0
  32. tgp_ui/templates/layout.html +59 -0
  33. tgp_ui/templates/layout2.html +99 -0
  34. tgp_ui/templates/macros.html +91 -0
  35. tgp_ui/templates/main.html +5 -0
  36. tgp_ui/templates/nxc_file_tree.html +33 -0
  37. tgp_ui/templates/nxc_login.html +25 -0
  38. tgp_ui/templates/nxc_tab.html +15 -0
  39. tgp_ui/templates/project.html +25 -0
  40. tgp_ui/templates/project_nxc.html +37 -0
  41. tgp_ui/templates/projects.html +73 -0
  42. tgp_ui/templates/publish.html +120 -0
  43. tgp_ui/templates/storage.html +49 -0
  44. tgp_ui/templates/tei_browser.html +14 -0
  45. tgp_ui/templates/tei_explorer.html +48 -0
  46. tgp_ui/templates/xpath_parser_modal_content.html +37 -0
tgp_ui/app.py ADDED
@@ -0,0 +1,424 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2023-2025 TU-Dresden (ZIH)
3
+ # ralf.klammer@tu-dresden.de
4
+ # moritz.wilhelm@tu-dresden.de
5
+
6
+ import logging
7
+
8
+ import click # type: ignore
9
+ import os
10
+ import shutil
11
+
12
+ from json import loads
13
+ from flask import Flask, render_template, request, redirect, session # type: ignore
14
+ from flask import url_for as default_url_for # type: ignore
15
+ from flask_json import json_response, FlaskJSON # type: ignore
16
+
17
+ from tgp_backend.directories import generateList
18
+ from tgp_backend.project import Project, TGProject
19
+
20
+ from tgp_backend.auth import SecretKeyManager
21
+ from tgp_backend.nextcloud import Nextcloud
22
+ from tgp_backend.util import config
23
+ from tgp_backend.util import remove_empty_strings_from_dict
24
+
25
+ from tg_model.tei import TEIParser # type: ignore
26
+
27
+ log = logging.getLogger(__name__)
28
+
29
+ base_params = {
30
+ "title": "TG Prepare",
31
+ }
32
+
33
+ MAIN_PATH = config("main", "path", default="./projects")
34
+
35
+ app = Flask(__name__)
36
+ FlaskJSON(app)
37
+
38
+
39
+ secret_manager = SecretKeyManager(MAIN_PATH)
40
+ app.secret_key = secret_manager.secret_key
41
+
42
+ # Additional security settings, if not in DEBUG mode
43
+ app.config.update(SESSION_COOKIE_NAME="tgp_nextcloud_session")
44
+ if config("log", "level", default="DEBUG") != "DEBUG":
45
+ app.config.update(
46
+ SESSION_COOKIE_SECURE=True, # Only allow cookies over HTTPS
47
+ SESSION_COOKIE_HTTPONLY=True, # Prevent JavaScript access to cookies
48
+ SESSION_COOKIE_SAMESITE="Strict", # Protect against CSRF attacks
49
+ )
50
+
51
+ app.jinja_env.globals.update(len=len, round=round)
52
+
53
+
54
+ # Function to retrieve the prefix from headers, used for reverse proxy setups
55
+ def get_prefix():
56
+ return request.headers.get(
57
+ "X-Forwarded-Prefix", request.headers.get("X-Script-Name", "")
58
+ )
59
+
60
+
61
+ # Custom implementation of Flask's url_for to include the prefix
62
+ def url_for(*args, **kwargs):
63
+ """Overrides Flask's url_for globally to include the prefix"""
64
+ return get_prefix() + default_url_for(*args, **kwargs)
65
+
66
+
67
+ # Context processor to inject the custom url_for into the Jinja2 templates
68
+ @app.context_processor
69
+ def inject_url_for():
70
+ return dict(url_for=url_for)
71
+
72
+
73
+ def get_projects():
74
+ projects = []
75
+ for sub in os.listdir(MAIN_PATH):
76
+ projectpath = "%s/%s" % (MAIN_PATH, sub)
77
+ if os.path.isdir(projectpath):
78
+ projects.append(Project(sub))
79
+ return projects
80
+
81
+
82
+ app.jinja_env.globals.update(title="TG Prepare", get_projects=get_projects)
83
+
84
+
85
+ def _startup():
86
+
87
+ # Create the projects directory if it does not exist
88
+ if not os.path.exists(MAIN_PATH):
89
+ os.makedirs(MAIN_PATH)
90
+
91
+ logging.getLogger("zeep").setLevel(logging.INFO)
92
+ app.run(
93
+ host=config("main", "host", default="0.0.0.0"),
94
+ port=config("main", "port", default=8077),
95
+ debug=config("log", "level", default="DEBUG") == "DEBUG",
96
+ )
97
+
98
+
99
+ @click.command()
100
+ @click.option("--path", "-p", default=None)
101
+ def startup(path):
102
+ base_params["path"] = path if path else os.getcwd()
103
+ _startup()
104
+
105
+
106
+ # *****************
107
+ # VIEWS
108
+ # *****************
109
+ @app.route(
110
+ "/sidebar",
111
+ methods=["GET", "POST"],
112
+ )
113
+ def sidebar():
114
+
115
+ return render_template(
116
+ "layout2.html",
117
+ )
118
+
119
+
120
+ @app.route(
121
+ "/xpath_parser_modal/<string:projectname>/<string:title>",
122
+ methods=["GET", "POST"],
123
+ )
124
+ def xpath_parser_modal(projectname=None, title=None):
125
+ project = Project(projectname)
126
+ collection = project.get_collection(title)
127
+ # collection_config = collection["config"]
128
+ collection_parser = collection["parser"]
129
+
130
+ return render_template(
131
+ "xpath_parser_modal_content.html",
132
+ # collection=collection_config,
133
+ collection_parser=collection_parser,
134
+ )
135
+
136
+
137
+ @app.route("/new_project", methods=["POST"])
138
+ def new_project():
139
+ project = Project(request.form.get("projectname"))
140
+ project.create()
141
+ return redirect(url_for("project", projectname=project.name))
142
+
143
+
144
+ @app.route("/upload_files/<string:projectname>", methods=["POST"])
145
+ def upload_files(projectname=None):
146
+ Project(projectname).file_upload(request.files.getlist("files"))
147
+
148
+ return redirect(url_for("project", projectname=projectname))
149
+
150
+
151
+ @app.route(
152
+ "/collection/<string:projectname>/<string:name>", methods=["GET", "POST"]
153
+ )
154
+ def collection(projectname, name):
155
+ project = Project(projectname)
156
+ collection = project.get_collection(name)
157
+ return render_template(
158
+ "collection.html",
159
+ project=project,
160
+ collection_title=name,
161
+ collection=collection["config"],
162
+ )
163
+
164
+
165
+ @app.route("/project/<string:projectname>", methods=["GET", "POST"])
166
+ def project(projectname=None):
167
+ params = {
168
+ "sub_title": "Project: %s" % projectname,
169
+ "sub_description": "",
170
+ }
171
+ params.update(base_params)
172
+
173
+ return render_template(
174
+ "project.html",
175
+ user=session.get("user", "-"),
176
+ current_project=projectname,
177
+ project=Project(projectname),
178
+ tab=request.args.get("tab"),
179
+ **params,
180
+ )
181
+
182
+
183
+ @app.route("/", methods=["GET"])
184
+ @app.route("/project", methods=["GET", "POST"])
185
+ @app.route("/projects", methods=["GET", "POST"])
186
+ def projects():
187
+ params = {
188
+ "sub_title": "Projects",
189
+ "sub_description": "",
190
+ }
191
+ params.update(base_params)
192
+
193
+ projectname = request.form.get("projectname")
194
+ fullpath = f"{MAIN_PATH}/{projectname}"
195
+
196
+ if request.method == "POST":
197
+ if request.form.get("delete"):
198
+ # Delete the project
199
+ if fullpath.strip("/") == MAIN_PATH.strip("/"):
200
+ log.error(f"Cannot delete main path ({MAIN_PATH})!")
201
+ elif os.path.exists(fullpath):
202
+ shutil.rmtree(fullpath)
203
+ else:
204
+ log.warning("Project does not exist!")
205
+
206
+ # Get list of all existing projects/directories
207
+ projects = []
208
+ for sub in os.listdir(MAIN_PATH):
209
+ projectpath = "%s/%s" % (MAIN_PATH, sub)
210
+ if os.path.isdir(projectpath):
211
+ projects.append(Project(sub))
212
+ # projects.append({"name": sub, "fullpath": projectpath})
213
+
214
+ return render_template("projects.html", projects=projects, **params)
215
+
216
+
217
+ @app.route(
218
+ "/tei_explorer/<string:projectname>/<string:title>", methods=["GET"]
219
+ )
220
+ def tei_explorer(projectname, title):
221
+ project = Project(projectname)
222
+ dir_list_dict, file_list_dict = generateList(
223
+ project.get_subproject_inpath(title)
224
+ )
225
+
226
+ return render_template(
227
+ "tei_explorer.html",
228
+ dir_list=dir_list_dict,
229
+ file_list=file_list_dict,
230
+ collection_title=title,
231
+ project=project,
232
+ )
233
+
234
+
235
+ # *****************
236
+ # JSON-REQUESTS
237
+ # *****************
238
+ @app.route("/load_tei_content", methods=["GET"])
239
+ def load_tei_content():
240
+ path = request.args.get("path")
241
+ log.debug("load_tei_content path: %s" % path)
242
+ _type = request.args.get("type")
243
+ if path is not None:
244
+ tei_parser = TEIParser(fullpath=path)
245
+ if _type == "header":
246
+ return json_response(
247
+ value="OK",
248
+ content=tei_parser.find(
249
+ "//teiHeader", node_as_text=True
250
+ ).decode("utf-8"),
251
+ )
252
+ elif _type == "text":
253
+ return json_response(
254
+ value="OK",
255
+ content=tei_parser.find(".//text", node_as_text=True).decode(
256
+ "utf-8"
257
+ ),
258
+ )
259
+ return json_response(value="Unknown type requested!")
260
+
261
+
262
+ @app.route(
263
+ "/set_configs/<string:projectname>/<string:title>", methods=["POST"]
264
+ )
265
+ def save_collection(projectname, title):
266
+ project = Project(projectname)
267
+ collection = project.get_collection(title)
268
+ collection_config = collection["config"]
269
+ for _attrib in request.form:
270
+ attrib = _attrib.replace("[]", "")
271
+ if attrib in collection_config.multi_attribs:
272
+ value = [
273
+ remove_empty_strings_from_dict(loads(v))
274
+ for v in request.form.getlist(_attrib)
275
+ ]
276
+ elif attrib in collection_config.xpath_or_value_attribs:
277
+ value = remove_empty_strings_from_dict(
278
+ loads(request.form.get(attrib))
279
+ )
280
+ else:
281
+ value = request.form.get(attrib)
282
+ setattr(collection_config, attrib, value)
283
+ collection_config.save()
284
+ return json_response(response="OK")
285
+
286
+
287
+ @app.route("/nextcloud_tab/", methods=["POST"])
288
+ def nextcloud_tab():
289
+ nextcloud = Nextcloud(**session)
290
+ return render_template(
291
+ "nxc_tab.html",
292
+ nextcloud=nextcloud if nextcloud.test_connection() else None,
293
+ user=session.get("username", "-"),
294
+ )
295
+
296
+
297
+ @app.route("/nextcloud_login/", methods=["POST"])
298
+ def nextcloud_login():
299
+
300
+ data = request.get_json()
301
+ login_data = data.get("data", [])
302
+
303
+ if Nextcloud(**login_data).test_connection():
304
+ for key in login_data:
305
+ session[key] = login_data[key]
306
+
307
+ return json_response(login_succes=True)
308
+
309
+
310
+ @app.route("/nextcloud_logout", methods=["POST"])
311
+ def nextcloud_logout():
312
+ session.clear()
313
+ return json_response(response="OK")
314
+
315
+
316
+ @app.route("/nextcloud_download", methods=["POST"])
317
+ def nextcloud_download():
318
+ data = request.get_json()
319
+ projectname = data.get("projectname")
320
+ file_paths = data.get("file_paths", [])
321
+ nxt = Nextcloud(**session)
322
+ nxt.download_nxc_files(file_paths, projectname)
323
+
324
+ return json_response(response="OK")
325
+
326
+
327
+ @app.route("/clone_git_project/<string:projectname>", methods=["POST"])
328
+ def clone_git_project(projectname=None):
329
+ Project(projectname).clone_git_project(request.form.get("github_repo"))
330
+
331
+ return json_response(response="OK")
332
+
333
+
334
+ @app.route(
335
+ "/check_xpath/<string:projectname>/<string:collectionname>",
336
+ methods=["GET"],
337
+ )
338
+ def check_xpath(projectname, collectionname):
339
+ project = Project(projectname)
340
+ collection = project.get_collection(collectionname)
341
+ collection_config = collection["config"]
342
+
343
+ xpath = request.args.get("xpath")
344
+
345
+ results = []
346
+
347
+ for file in collection_config.elements:
348
+ tei_parser = TEIParser(fullpath=file["fullpath"])
349
+ result = tei_parser.find(xpath)
350
+ if result is not None:
351
+ results.append({"filename": file["filename"], "result": result})
352
+
353
+ return json_response(value="OK", results=results)
354
+
355
+
356
+ @app.route("/set_configs/<string:projectname>", methods=["POST"])
357
+ def set_configs(projectname):
358
+ Project(projectname).init_configs(request.form.getlist("tei_directories"))
359
+
360
+ return json_response(response="OK")
361
+
362
+
363
+ @app.route(
364
+ "/publish_project/<string:projectname>/<string:instance>", methods=["POST"]
365
+ )
366
+ def publish_project(projectname, instance):
367
+ TGProject(projectname, instance).publish_tg_project()
368
+ return json_response(response="OK")
369
+
370
+
371
+ @app.route(
372
+ "/save_session_id/<string:projectname>/<string:instance>", methods=["POST"]
373
+ )
374
+ def save_session_id(projectname, instance):
375
+ TGProject(projectname, instance).tg_session_id = request.form.get(
376
+ "tg_auth_session_id"
377
+ )
378
+
379
+ return json_response(response="OK")
380
+
381
+
382
+ @app.route(
383
+ "/save_tg_project_id/<string:projectname>/<string:instance>",
384
+ methods=["POST"],
385
+ )
386
+ def save_tg_project_id(projectname, instance):
387
+ TGProject(projectname, instance).tg_project_id = request.form.get(
388
+ "tg_project_id"
389
+ )
390
+ return json_response(response="OK")
391
+
392
+
393
+ @app.route(
394
+ "/delete_tg_project_id/<string:projectname>/<string:instance>/<string:tg_project_id>",
395
+ methods=["POST"],
396
+ )
397
+ def delete_tg_project_id(projectname, instance, tg_project_id):
398
+ TGProject(projectname, instance).delete_tg_project(tg_project_id)
399
+
400
+ return json_response(response="OK")
401
+
402
+
403
+ @app.route(
404
+ "/get_tg_project_hits/<string:projectname>/<string:instance>/<string:tg_project_id>",
405
+ methods=["GET"],
406
+ )
407
+ def get_tg_project_hits(projectname, instance, tg_project_id):
408
+ hits = TGProject(projectname, instance).get_tg_project_hits(tg_project_id)
409
+ return json_response(response="OK", hits=hits)
410
+
411
+
412
+ @app.route(
413
+ "/create_tg_project/<string:projectname>/<string:instance>",
414
+ methods=["POST"],
415
+ )
416
+ def create_tg_project(projectname, instance):
417
+ TGProject(projectname, instance).create_tg_project(
418
+ request.form.get("tg_projectname")
419
+ )
420
+ return json_response(response="OK")
421
+
422
+
423
+ if __name__ == "__main__":
424
+ startup()